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.
- package/AGENTS.md +11 -6
- package/CHANGELOG.md +56 -4
- package/CONTRIBUTING.md +12 -2
- package/DESIGN.md +7 -4
- package/README.md +2 -2
- package/SECURITY.md +8 -0
- package/dist/commands/config.js +17 -0
- package/dist/commands/meld.js +38 -10
- package/dist/commands/spawn.js +11 -4
- package/dist/commands/token.js +2 -1
- package/dist/daemon/archive-gate.js +11 -0
- package/dist/daemon/server.js +112 -89
- package/dist/db/schema.js +10 -0
- package/dist/db/tokens.js +43 -2
- package/dist/shared/app-origin.js +26 -0
- package/dist/shared/narrative-menu.js +16 -11
- package/dist/shared/view-url.js +39 -20
- package/dist/test/app-host-literal.test.js +53 -0
- package/dist/test/archive-gate.test.js +22 -0
- package/dist/test/cors-preflight.test.js +28 -21
- package/dist/test/hook-config.test.js +38 -1
- package/dist/test/meld.test.js +59 -1
- package/dist/test/narrative-menu.test.js +30 -3
- package/dist/test/remote-meld.test.js +13 -0
- package/dist/test/terminal-size.test.js +36 -0
- package/dist/test/token-devices.test.js +59 -0
- package/dist/test/view-url.test.js +93 -47
- package/dist/test/wrangler-route.test.js +27 -0
- package/dist/tmux.js +11 -0
- package/docker-entrypoint.sh +15 -2
- package/docs/daemon-api.md +5 -5
- package/docs/mcp.md +1 -1
- package/docs/releasing.md +62 -9
- package/docs/terminal-mirroring.md +32 -0
- package/docs/token-rotation.md +26 -6
- package/docs/view-urls.md +32 -7
- package/docs/web-app.md +2 -2
- package/expo/dist/_expo/static/js/web/{entry-e56efc1291dca7258ea00b8108b033a4.js → entry-807575a48b8a2f18f8bffe53c8b35fbd.js} +47 -45
- package/expo/dist/index.html +1 -1
- package/expo/dist/sw.js +2 -2
- package/package.json +1 -1
- 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
|
|
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-
|
|
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.
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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** —
|
|
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)
|