diff --git a/PLAN.md b/PLAN.md index 06d89e0..478c634 100644 --- a/PLAN.md +++ b/PLAN.md @@ -47,6 +47,30 @@ them, in one commit: - `o` in a space with templates offers them on the line, `:new `; `Enter` on nothing is still a blank document. +## The phone app, done + +Written on 2026-10-22. The workspace installs on a phone as an app: + +- A manifest (`web/public/manifest.webmanifest`) with a 192 and a 512 icon and + a maskable one, drawn from the favicon. +- A service worker (`web/src/sw.ts`, built by a small plugin in + `vite.config.ts`) keeps index.html, the logo, the entry with what it imports, + and the latin fonts, and every hashed asset once it has been fetched. Pages + are asked of the server first and answered from the cache when it is down. + It never touches `/api`. The CSP names `worker-src` and `manifest-src`. +- A share target, `POST /share`, caught by the worker and never sent to the + server: the text waits in the cache `share` and becomes a scratchpad note + once someone is logged in. +- Back, on a touch screen, lets go of one thing at a time through one guard + entry in the history (`back()` in `modes.ts`), and leaves the app at the + root of a space. +- The bar and the band grow by the safe-area insets, so an installed app keeps + them clear of the status bar and the gesture bar. + +Not done, on purpose: reading documents offline, and sharing a photo or a file +— a file needs a document or a folder to belong to, and a note has neither. +iOS has no share target, so the iPhone keeps the Shortcut. + ## Fixed before, on 2026-10-21 After the seven slices of the feature plan (trash `8ffad63`, revisions @@ -83,9 +107,10 @@ tab. `.github/workflows/wiki.yml` replaces it on its first run. The backlog, fitted to the model rather than given new surfaces: -- Install the app on a phone (a PWA: manifest plus a service worker for the - app shell, CSP `worker-src 'self'`), with a share target that posts into - capture as a note. The touch and desktop menus are already the same. +- Read on a phone with no network: the documents opened last, kept by the + service worker or in IndexedDB, and cleared on logout. +- Share a photo or a file into the app: it asks which document or folder it + belongs to. - Task lists: toggle `- [ ]` in the reader, and the open ones of a space as a section of the agenda rather than a `:tasks` command. - A daily review, also as a section of the agenda: yesterday's entry, what was @@ -95,4 +120,4 @@ The backlog, fitted to the model rather than given new surfaces: Dropped: marks (`ma` … `mz`). The info sheet's headings and the tiles cover what they were for, and `m` stays picking nodes up. -Suggested next: the PWA. +Suggested next: task lists and the daily review, as sections of the agenda. diff --git a/README.md b/README.md index f556ffb..45a9569 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,9 @@ document per day. A file sits in a folder or belongs to its document. card, or more of a document. - **Capture.** A token per phone or script that puts a card in an inbox, a note on the scratchpad or a line in today's entry, and nothing else. +- **Install.** On a phone the workspace installs as an app: it opens with no + network, sits in the share sheet, where whatever is shared to it lands on + the scratchpad, and back closes one thing at a time. - **Share.** Any document behind an unguessable, revocable read-only link. - **Mount.** The whole workspace over SFTP, for an editor on another machine. - **Back up.** A mirror to Proton Drive, end-to-end encrypted, as a registered @@ -223,6 +226,18 @@ has today's entry, an upload into the folder, the trash, a search across every s and on the board the card moves, renames and deletes; the agenda has a done button per card. +Over HTTPS the browser offers to install the workspace (Chrome's menu: *Install +app*, or *Add to Home screen*). Installed, it opens in a window of its own, from +its own icon, and with no network it still opens, to say the server cannot be +reached and try again. Back — the gesture or the button — lets go of one thing +at a time, the way `Esc` does: a sheet, the scratchpad, the editor, the +document, then a folder up; at the root of a space it leaves the app. On +Android the installed app is in the share sheet: a link or some text shared to +it becomes a note on the scratchpad, the title on its first line, and waits +there through a login if the session has run out. A service worker keeps the +app's own files and nothing else; it never stores a document, a card or a +note. + ## Tiles Several documents can be open at once. `L` (or `Shift-Enter`) on a row opens @@ -416,7 +431,8 @@ On an iPhone, a Shortcut with *Ask for Input* followed by *Get Contents of URL* does it: method `POST`, a header `Authorization` of `Bearer nwc_…`, and a JSON body with `text` set to the input. On Android, *HTTP Shortcuts* does the same with a text-input variable in the body. Either one goes on the home -screen, or behind the share sheet. +screen, or behind the share sheet. On Android the installed app is in the share +sheet itself, with no token, and puts what is shared on the scratchpad. ## Not losing things @@ -951,7 +967,9 @@ sealed or not. ## Deploying Put a TLS-terminating reverse proxy in front of it, point it at `WORKSPACE_BIND`, -and set `WORKSPACE_SECURE_COOKIE=1`. Nothing else is needed: no database server, no +and set `WORKSPACE_SECURE_COOKIE=1`. HTTPS is also what lets a phone install the +app: a browser runs a service worker only over HTTPS, or on `localhost`. +Nothing else is needed: no database server, no runtime dependencies, no build tools on the host. The server will not start while `WORKSPACE_SECRET` or `WORKSPACE_PASSWORD` is still the example from `.env.example`: with the example secret anyone could sign themselves a session. @@ -972,7 +990,8 @@ everyone out once. The app sends its own `Content-Security-Policy`, `Referrer-Policy: no-referrer`, `X-Content-Type-Options`, `X-Frame-Options`, `Cross-Origin-Opener-Policy`, `Cross-Origin-Resource-Policy` and a `Permissions-Policy` that turns off camera, -microphone and location; API answers are `Cache-Control: no-store` unless they +microphone and location. The policy lets in one worker and one manifest, the +app's own (`/sw.js`, `/manifest.webmanifest`); API answers are `Cache-Control: no-store` unless they say otherwise. A proxy that adds its own should not weaken them. `no-referrer` matters in particular, because a share token lives in the URL and would otherwise leak to any site a shared entry links to. Share links minted before the key existed are revoked by migration `0005`; diff --git a/docs/wiki/Capture.md b/docs/wiki/Capture.md index eedac4f..c5b703e 100644 --- a/docs/wiki/Capture.md +++ b/docs/wiki/Capture.md @@ -60,3 +60,8 @@ with a text-input variable, method `POST`, the same header, and a JSON body `{"text": "{{input}}"}`. Either one can sit on the home screen, or behind the share sheet. + +**Android, without a token**: install the workspace from Chrome (*Install app*, +over HTTPS) and it is in the share sheet itself. What is shared to it — a link, +some text, a title — becomes a note on the scratchpad, from the session the app +is logged in with. See [On a phone](Guide.md#on-a-phone). diff --git a/docs/wiki/Getting-Started.md b/docs/wiki/Getting-Started.md index d40ec4a..8fd223f 100644 --- a/docs/wiki/Getting-Started.md +++ b/docs/wiki/Getting-Started.md @@ -80,6 +80,9 @@ The rail of buttons on the right does the same as the keys, for a mouse. On a phone the same things are buttons: the dock at the bottom has a new document, a new folder, the board, today's entry, an upload and a search, and the `⋯` on a row (or a long press on it) has what can be done to that row. +Over HTTPS the browser offers to install it as an app, which then sits in the +share sheet and puts what is shared on the scratchpad; see +[On a phone](Guide.md#on-a-phone). ![The workspace on a phone](../images/mobile-tree.png) diff --git a/docs/wiki/Guide.md b/docs/wiki/Guide.md index 6216832..6573a72 100644 --- a/docs/wiki/Guide.md +++ b/docs/wiki/Guide.md @@ -205,7 +205,9 @@ curl -H "Authorization: Bearer nwc_…" -H 'content-type: application/json' \ -d '{"text":"call the bank"}' https://your-host/api/capture ``` -The token can do this and nothing else. See [Capture](Capture.md). +The token can do this and nothing else. On Android the installed app needs no +token: share to it, and the text is a note on the scratchpad. See +[Capture](Capture.md) and [On a phone](#on-a-phone). ## Evening: the diary @@ -251,3 +253,15 @@ has a done button on each card. | ![The tree on a phone](../images/mobile-tree.png) | ![A document on a phone](../images/mobile-document.png) | `:set vim` turns the vim keys on for a phone with a keyboard. + +Over HTTPS the browser offers to install the workspace as an app (Chrome's +menu: *Install app*, or *Add to Home screen*). Installed, it opens from its own +icon, and with no network it still opens, to say the server cannot be reached. +Back lets go of one thing at a time, as `Esc` does — a sheet, the scratchpad, +the editor, the document, then a folder up — and only at the root of a space +leaves the app. + +On Android the installed app is in the share sheet. A link or some text shared +to it becomes a note on the scratchpad, the title on its first line; the drawer +comes down with it on top, ready to be kept, made a card or put at the end of a +document. A share made while logged out waits for the login. diff --git a/src/main.rs b/src/main.rs index ceb476f..ab85c2e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -406,9 +406,12 @@ fn compressible( /// Content-Security-Policy for the app shell. `unsafe-inline` is granted to /// styles only, because CodeMirror injects its theme as a