brane 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/AGENTS.md +11 -6
  2. package/CHANGELOG.md +56 -4
  3. package/CONTRIBUTING.md +12 -2
  4. package/DESIGN.md +7 -4
  5. package/README.md +2 -2
  6. package/SECURITY.md +8 -0
  7. package/dist/commands/config.js +17 -0
  8. package/dist/commands/meld.js +38 -10
  9. package/dist/commands/spawn.js +11 -4
  10. package/dist/commands/token.js +2 -1
  11. package/dist/daemon/archive-gate.js +11 -0
  12. package/dist/daemon/server.js +112 -89
  13. package/dist/db/schema.js +10 -0
  14. package/dist/db/tokens.js +43 -2
  15. package/dist/shared/app-origin.js +26 -0
  16. package/dist/shared/narrative-menu.js +16 -11
  17. package/dist/shared/view-url.js +39 -20
  18. package/dist/test/app-host-literal.test.js +53 -0
  19. package/dist/test/archive-gate.test.js +22 -0
  20. package/dist/test/cors-preflight.test.js +28 -21
  21. package/dist/test/hook-config.test.js +38 -1
  22. package/dist/test/meld.test.js +59 -1
  23. package/dist/test/narrative-menu.test.js +30 -3
  24. package/dist/test/remote-meld.test.js +13 -0
  25. package/dist/test/terminal-size.test.js +36 -0
  26. package/dist/test/token-devices.test.js +59 -0
  27. package/dist/test/view-url.test.js +93 -47
  28. package/dist/test/wrangler-route.test.js +27 -0
  29. package/dist/tmux.js +11 -0
  30. package/docker-entrypoint.sh +15 -2
  31. package/docs/daemon-api.md +5 -5
  32. package/docs/mcp.md +1 -1
  33. package/docs/releasing.md +62 -9
  34. package/docs/terminal-mirroring.md +32 -0
  35. package/docs/token-rotation.md +26 -6
  36. package/docs/view-urls.md +32 -7
  37. package/docs/web-app.md +2 -2
  38. package/expo/dist/_expo/static/js/web/{entry-e56efc1291dca7258ea00b8108b033a4.js → entry-807575a48b8a2f18f8bffe53c8b35fbd.js} +47 -45
  39. package/expo/dist/index.html +1 -1
  40. package/expo/dist/sw.js +2 -2
  41. package/package.json +1 -1
  42. package/wiki/shared/index.md +5 -0
package/docs/view-urls.md CHANGED
@@ -3,7 +3,7 @@
3
3
  The query string of a narrative screen is not decoration — it is the only way
4
4
  a view travels. Active tab, pinned tabs, open file and line range are kept in
5
5
  `sessionStorage`, which is **origin-scoped**, and two menu items cross an
6
- origin: **Isolate** hops between `brane.organelle.dev` and a daemon's own
6
+ origin: **Isolate** hops between a shared app's origin and a daemon's own
7
7
  bundle, and **Meld** hands a URL to another device. Whatever is not in the URL
8
8
  does not arrive.
9
9
 
@@ -15,16 +15,36 @@ tab, pin subset, path and line spec):
15
15
  |---|---|
16
16
  | `viewParams()` | The view as params, `undefined` for anything at its default — which is how `router.setParams` clears one |
17
17
  | `parseActiveTab()`, `parsePinnedTabs()`, `parseLineSpec()` | Read them back. Writer and reader live together on purpose: a param whose two halves sit in different modules drifts |
18
+ | `isIsolated(pageHost, daemonHost)` | Whether this page is the daemon's own bundle: `location.host` against the connection's host, no constant in it |
18
19
  | `buildViewUrl(target, opts)` | The same narrative, same view, somewhere else |
19
- | `buildIsolateUrl()` / `buildMeldUrl()` | The same builder with one bit different — see below |
20
+ | `buildIsolateUrl()` / `buildMeldUrl()` | The same builder with one bit different — see below. Isolate is `null` from a daemon's bundle that reported no app host |
20
21
  | `withoutConnectionParams()`, `withoutConnectionHash()` | Strip `host`/`token` from the query string and from the fragment, and leave everything else |
21
22
  | `connectionFragment()` | `#host=…&token=…` for the shared app, `#token=…` for a daemon's own bundle — the one place the connection is written into a URL, used by `buildViewUrl` and by `brane meld` |
22
23
 
23
24
  **Isolate flips the origin kind; Meld stays on it.** Isolate exists to change
24
- which app serves you. Meld aims at another *device* — hand a phone a shared-SPA
25
+ which app serves you. Meld aims at another *device* — hand a phone a shared-app
25
26
  link while the operator is reaching the daemon directly, and the phone loads an
26
27
  app that has never heard of that daemon.
27
28
 
29
+ **The app knows no host.** There is no constant naming `brane.organelle.dev`
30
+ in the bundle, and `src/test/app-host-literal.test.ts` keeps it that way.
31
+ "Isolated" means *this page is served by the daemon it is connected to*:
32
+ `window.location.host` equal to the connection's host, which a daemon's own
33
+ bundle fills from the same `location.host`. Everything else is a shared app,
34
+ whichever host serves it: the hosted shell, a staging hostname, a fork's
35
+ deployment, a dev server on `localhost`. From a shared app, Meld stays on
36
+ *that* origin and Isolate hops in to the daemon; neither needs to be told a
37
+ host. From a daemon's bundle, Isolate hops *out* to whatever the daemon
38
+ reports as its `app_domain` in `GET /api/status` (`useAppHost`,
39
+ `expo/lib/appHost.ts`, fetched before the tap, since a `window.open` after an
40
+ await is a popup the browser blocks). A daemon that reports none, or cannot
41
+ be reached for it, has no way out, and the menu leaves Isolate out rather
42
+ than hand you a link to a host the app made up. `brane meld` reads the same
43
+ config, so a fork changes `CONFIG_DEFAULTS.app_domain` and both its CLI and
44
+ its daemons name its own shell. None of this is trust: the daemon accepts any
45
+ origin (`SECURITY.md`, Authentication); the host only decides which copy of
46
+ the app a browser loads.
47
+
28
48
  **Back undoes a jump, never a view change.** A jump pushes a history entry: to
29
49
  a different narrative (from the list, an `@nid` link, the switcher, Revive or
30
50
  Twin), and to a message by `nid:seq`, *including one in the narrative you are
@@ -37,7 +57,12 @@ since the stack did not grow. So the URL keeps that state and Back returns to
37
57
  it, but it never costs an extra Back: browsing ten files is one entry, not ten.
38
58
  A bare link to the narrative you are in, with no seq, goes nowhere. The list
39
59
  is a jump too: **All narratives** in the switcher pushes it, so Back returns
40
- to the narrative, whether or not a list was already beneath. The header has no
60
+ to the narrative, whether or not a list was already beneath. So does a
61
+ swipe down on the narrative's header card, which dismisses it. So does
62
+ **Archive** in the narrative's 🦠 menu, once the daemon has confirmed it (the
63
+ pushed list fetches on mount, and nothing is broadcast to correct a fetch that
64
+ raced the archive); Back returns to the archived narrative. Archiving from the
65
+ list's own menu just drops the row and goes nowhere. The header has no
41
66
  ← of its own; Back is the browser's.
42
67
 
43
68
  Back is instant and complete because the Stack keeps the screens behind the
@@ -86,9 +111,9 @@ by resetting to the bare pathname, which would throw away the view that just
86
111
  arrived.
87
112
 
88
113
  **The connection rides in the fragment; the view rides in the query string.**
89
- A query string is part of the HTTP request. On a `brane.organelle.dev` link
90
- that meant the daemon's bearer token and tailnet hostname reached Cloudflare's
91
- edge with every meld; on an Isolate hop it landed in the daemon's own request
114
+ A query string is part of the HTTP request. On a shared-app link that meant
115
+ the daemon's bearer token and tailnet hostname reached the shell host's edge
116
+ with every meld; on an Isolate hop it landed in the daemon's own request
92
117
  log; and a meld URL pasted into iMessage or Slack handed it to the link
93
118
  unfurler. A fragment is never sent to any server, so `#host=…&token=…` is what
94
119
  every link carries now (`connectionFragment`), and `?token=` is read only for
package/docs/web-app.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Web App (Expo)
2
2
 
3
- The Expo app runs as a web PWA served by the daemon (and optionally via Cloudflare Workers at `brane.organelle.dev`). It provides:
3
+ The Expo app runs as a web PWA served by the daemon (and optionally via Cloudflare Workers at `brane.organelle.dev`, which serves the app of the `latest` npm release and is deployed only by a release — see [The app ships with the release](releasing.md#the-app-ships-with-the-release)). It provides:
4
4
 
5
5
  - **Narrative list** — shows all narratives with summary, status, and MCP connection indicator
6
6
  - **Spawn button** — header button that opens fold picker modal (or spawns directly with 1 fold)
@@ -13,7 +13,7 @@ The Expo app runs as a web PWA served by the daemon (and optionally via Cloudfla
13
13
  - **Quick bar** — macros (via MacroMenu component) + quick keys
14
14
  - **Full software keyboard** — multitouch with sticky/held modifiers
15
15
  - **Live updates** — control WebSocket pushes `status`, `meta`, and `new_events` hints
16
- - **Archive/unarchive** — swipe or long-press to archive narratives
16
+ - **Archive/unarchive** — from the 🦠 menu, on a list card's status badge or in the narrative's header. Archive is offered only once the session has ended (the same test that swaps End for Revive); Unarchive on any archived narrative. Archiving from the header goes to the list as a jump, so Back returns — see [View URLs](view-urls.md)
17
17
  - **Payload rendering** — inline display of image payloads in chat
18
18
  - **Export** — download a narrative as standalone HTML via `/api/narratives/:id/export?format=html`. Foreign rows carry their speaker's hue inline, from the same `speakerColor` / `speakerBgColor` the chat uses, at the 🎨 Recolor salt the device sends as `&palette=` — the one place the salt leaves the browser. With messages selected, the menu offers both sides of the selection — **those messages only**, or the conversation with **those messages left out** — see [Export Scope](export-scope.md)
19
19
  - **Brane switcher** — dropdown to filter narratives by brane, add/switch daemons. Each brane row (and "All branes") shows its running metered cost as `$2441.72 (138) + 100.2 AIC (1)` — one total per unit with the number of sessions it covers, so a per-session figure can be read off without the line asserting one; the list screen re-reads `/api/branes` (debounced) when a cost `meta` arrives, since the total includes archived narratives the screen never loads. **Devices** lists every token on the active daemon (this device, this machine's CLI and agents, each melded device by browser, platform, address and when it was last seen, and any meld QR not yet scanned) with a Revoke on each other device; **Sign out other devices**, at the foot of that list, rotates instead — see [Token Rotation](token-rotation.md#the-devices-list)