@unifedev/thread-pages 0.3.2 → 1.1.0

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 (95) hide show
  1. package/README.md +77 -129
  2. package/dist/server.js +11779 -11801
  3. package/dist/server.meta.json +2 -2
  4. package/docs/B1-OWN-FILES.md +133 -0
  5. package/docs/FOR-PAGE-AUTHORS-1.1.md +167 -0
  6. package/docs/UPGRADING.md +53 -0
  7. package/package.json +26 -18
  8. package/server.ts +3 -2175
  9. package/src/agent/cli.ts +193 -0
  10. package/src/agent/guide.ts +450 -0
  11. package/src/agent/instruction.ts +59 -0
  12. package/src/agent/seed/seed.ts +73 -0
  13. package/{theme.ts → src/agent/seed/theme-css.ts} +9 -11
  14. package/src/agent/starter-hub.ts +217 -0
  15. package/src/bb/activity.ts +59 -0
  16. package/src/bb/bb-host.ts +280 -0
  17. package/src/bb/public-origin.ts +45 -0
  18. package/src/config/settings.ts +82 -0
  19. package/src/domain/capabilities/contract.ts +48 -0
  20. package/src/domain/capabilities/index.ts +10 -0
  21. package/src/domain/capabilities/protocol.ts +113 -0
  22. package/src/domain/capabilities/registry.ts +48 -0
  23. package/src/domain/capabilities/renamed.ts +34 -0
  24. package/src/domain/capabilities/schema.ts +198 -0
  25. package/src/domain/capabilities/specs.ts +479 -0
  26. package/src/domain/eligibility.ts +43 -0
  27. package/src/domain/errors.ts +116 -0
  28. package/src/domain/html/document.ts +109 -0
  29. package/src/domain/html/escape.ts +16 -0
  30. package/src/domain/ids.ts +37 -0
  31. package/src/domain/json/canonical.ts +19 -0
  32. package/src/domain/json/strict-json.ts +139 -0
  33. package/src/domain/limits.ts +98 -0
  34. package/src/domain/rate-limit.ts +64 -0
  35. package/src/domain/revision.ts +27 -0
  36. package/src/domain/submissions/idempotency.ts +59 -0
  37. package/src/domain/submissions/message.ts +42 -0
  38. package/src/domain/submissions/parse.ts +105 -0
  39. package/src/domain/tokens/action-token.ts +52 -0
  40. package/src/domain/tokens/confirmation.ts +99 -0
  41. package/src/domain/tokens/mac.ts +50 -0
  42. package/src/generated/kernel-runtime.ts +3 -0
  43. package/src/generated/shell-runtime.ts +3 -0
  44. package/src/host/contract.ts +65 -0
  45. package/src/host/types.ts +89 -0
  46. package/src/pages/inline.ts +277 -0
  47. package/src/pages/layout.ts +65 -0
  48. package/src/pages/page-store.ts +170 -0
  49. package/src/pages/site.ts +36 -0
  50. package/src/plugin.ts +81 -0
  51. package/src/runtime/kernel/anchors.ts +45 -0
  52. package/src/runtime/kernel/api.ts +15 -0
  53. package/src/runtime/kernel/bridge-client.ts +148 -0
  54. package/src/runtime/kernel/dirty.ts +51 -0
  55. package/src/runtime/kernel/forms.ts +114 -0
  56. package/src/runtime/kernel/install.ts +156 -0
  57. package/src/runtime/kernel/labels.ts +98 -0
  58. package/src/runtime/kernel/main.ts +6 -0
  59. package/src/runtime/kernel/readonly.ts +75 -0
  60. package/src/runtime/shared/protocol.ts +125 -0
  61. package/src/runtime/shell/confirm.ts +70 -0
  62. package/src/runtime/shell/install.ts +79 -0
  63. package/src/runtime/shell/main.ts +12 -0
  64. package/src/runtime/shell/navigate.ts +64 -0
  65. package/src/runtime/shell/poll.ts +125 -0
  66. package/src/runtime/shell/relay.ts +185 -0
  67. package/src/serving/action-request.ts +32 -0
  68. package/src/serving/bridge/dispatcher.ts +112 -0
  69. package/src/serving/bridge/handler.ts +37 -0
  70. package/src/serving/bridge/handlers/index.ts +26 -0
  71. package/src/serving/bridge/handlers/navigation.ts +43 -0
  72. package/src/serving/bridge/handlers/reads.ts +186 -0
  73. package/src/serving/bridge/handlers/writes.ts +175 -0
  74. package/src/serving/bridge/selection-store.ts +58 -0
  75. package/src/serving/bridge-route.ts +23 -0
  76. package/src/serving/context.ts +34 -0
  77. package/src/serving/document-route.ts +37 -0
  78. package/src/serving/home-route.ts +23 -0
  79. package/src/serving/responses.ts +81 -0
  80. package/src/serving/routes.ts +26 -0
  81. package/src/serving/session-access.ts +22 -0
  82. package/src/serving/shell-html.ts +77 -0
  83. package/src/serving/shell-route.ts +51 -0
  84. package/src/serving/signing-key.ts +25 -0
  85. package/src/serving/submit-route.ts +47 -0
  86. package/src/serving/upload-route.ts +46 -0
  87. package/tsconfig.json +10 -6
  88. package/ARCHITECTURE.md +0 -230
  89. package/PLUGIN_OVERVIEW.md +0 -83
  90. package/authoring.ts +0 -368
  91. package/bridge.ts +0 -1721
  92. package/docs/MODEL.md +0 -211
  93. package/docs/ROADMAP.md +0 -96
  94. package/home.ts +0 -419
  95. package/page.ts +0 -782
package/ARCHITECTURE.md DELETED
@@ -1,230 +0,0 @@
1
- # Architecture
2
-
3
- Version 0.3.0 · September 2026
4
-
5
- This is the design and its reasoning. [docs/MODEL.md](./docs/MODEL.md) is the
6
- operator's view — where things are stored and how to reach them.
7
- [docs/ROADMAP.md](./docs/ROADMAP.md) is what is left.
8
-
9
- ## Intent
10
-
11
- A Thread Page is a small application an agent writes for one task, for one
12
- person to read and answer from.
13
-
14
- Chat is a poor medium for the moments that matter: a comparison, a diagram, a set
15
- of choices, a thing only the user can decide. Those want a page. But a *fixed*
16
- page — a dashboard with slots — is worse than chat, because the shape of what
17
- needs saying changes with every task.
18
-
19
- So the plugin supplies no design. It supplies a secure host for a document the
20
- agent writes freshly each time, and a way for that document to talk back.
21
-
22
- Three properties follow, and everything else is downstream of them:
23
-
24
- 1. **The page is a file the agent edits directly.** Not a template it fills, not
25
- an API it posts to. Saving is publishing.
26
- 2. **The agent's contract stays small.** One command, a short instruction, and
27
- an optional guide it fetches only when the page needs more than prose.
28
- 3. **Page code is untrusted.** It is generated, so it is sandboxed and given
29
- narrow named capabilities rather than credentials.
30
-
31
- ## The shape
32
-
33
- ```
34
- ~/.bb/thread-storage/<threadId>/thread-page.html the page
35
- /thread-page-assets/ what it shows
36
- /thread-page-uploads/ what the user sent
37
- ```
38
-
39
- An agent runs `bb thread-page init`, gets that path, and edits the file. The
40
- plugin serves it at a stable URL on the bb origin the user is already
41
- authenticated to — so the same link works on a laptop and a phone with no extra
42
- port, tunnel, or password.
43
-
44
- Nothing else is stored. Two small values live in bb's existing key-value table: a
45
- signing key, so open browser sessions survive a plugin reload, and a best-effort
46
- last-good copy per page, so a page still opens read-only when its machine is
47
- offline. There is no plugin database and no background service.
48
-
49
- ## Trust boundary
50
-
51
- The page is generated code, so it is treated as hostile.
52
-
53
- **Inside the iframe** — `sandbox="allow-scripts allow-forms"`, opaque origin.
54
- Arbitrary HTML, CSS and JavaScript are allowed *because* the frame has no bb
55
- cookie, no mutation token, no parent DOM, no `localStorage`, no raw bb API, no
56
- CLI, and no filesystem access. CSP blocks ordinary `fetch` and subresources.
57
- Page-authored code can therefore be as creative as the task needs without that
58
- creativity being a security question.
59
-
60
- **Outside the iframe** — plugin-authored code on the bb origin. It holds the
61
- action token, makes the same-origin calls, owns navigation, and renders
62
- confirmations. The document URL carries render authority only, so page code never
63
- sees a credential that can change anything.
64
-
65
- **Between them** — one `MessagePort` and a fixed capability list. Each capability
66
- has its own validator, size and depth limits, effect class, and output
67
- projection. There is no generic "call bb" method, no path parameter, and no
68
- provider passthrough.
69
-
70
- ### Confirmed effects
71
-
72
- Anything that reaches outside the current thread requires a confirmation the page
73
- cannot fake or word:
74
-
75
- 1. The page invokes the method.
76
- 2. The server refuses once, returning a signed challenge that carries **its own**
77
- summary, derived from validated parameters.
78
- 3. The trusted shell shows that summary in a dialog the sandbox cannot draw over.
79
- 4. The server verifies the signature before acting.
80
-
81
- The challenge is bound to one request id, method, parameter fingerprint, page
82
- revision and thread, and expires in two minutes. So it cannot be forged, replayed,
83
- or reused to approve different parameters — a page cannot get "message thread A"
84
- approved and then quietly reuse it for thread B.
85
-
86
- ### The honest limitation
87
-
88
- Page JavaScript can navigate its own frame and encode data in the destination
89
- URL. Browser sandbox flags do not close that channel, and CSP resource directives
90
- do not either. A page therefore has access to data already inside its own frame.
91
-
92
- It grants no bb authority. Closing it entirely would mean forbidding authored
93
- JavaScript and shipping a declarative renderer instead, which would cost the
94
- open-page model that is the point of the product. This is a stated trade, not an
95
- oversight.
96
-
97
- ## Capabilities
98
-
99
- | Method | Effect | Confirmed |
100
- | --- | --- | --- |
101
- | `context.get` | read | |
102
- | `thread.activity` | read | |
103
- | `threads.snapshot` | read | |
104
- | `projects.list` | read | |
105
- | `providers.list` | read | |
106
- | `storage.get` | read | |
107
- | `thread.reply` | current-thread write | |
108
- | `storage.set` | current-thread write | |
109
- | `threads.openPage` | navigation | |
110
- | `threads.openBb` | navigation | |
111
- | `threads.continue` | cross-thread write | yes |
112
- | `threads.spawn` | cross-thread write | yes |
113
- | `projects.create` | cross-thread write | yes |
114
- | `threads.archive` | destructive | yes |
115
- | `threads.stop` | destructive | yes |
116
- | `navigation.openExternal` | navigation | yes |
117
- | `projects.browse` | device | yes |
118
- | `voice.captureAndTranscribe` | device | contract only |
119
-
120
- Adding a capability extends this list. It never requires a new page component,
121
- because no page component is built in.
122
-
123
- Two deliberate narrowings. `projects.browse` opens the host's native folder
124
- picker and returns an **opaque single-use token** plus a display string — never a
125
- filesystem path the page could reuse or leak; `projects.create` redeems it.
126
- `storage.*` is namespaced per thread, so pages cannot read each other's state
127
- despite sharing one table.
128
-
129
- ## What the plugin renders, and what it does not
130
-
131
- The plugin owns exactly three pieces of UI, all of them chrome outside the
132
- sandbox:
133
-
134
- - the page title bar;
135
- - a **Sessions** link back to the home page;
136
- - a working indicator while the thread is mid-turn.
137
-
138
- The last two are worth explaining, because both could have been pushed onto
139
- agents and deliberately were not.
140
-
141
- **The Sessions link** is chrome so that no agent spends instruction budget on it
142
- and no page can forget it. The home page it points to is not special: any
143
- thread's page can be designated home with `bb thread-page home`, and it is an
144
- ordinary Thread Page afterwards.
145
-
146
- **The working indicator** answers "is it still writing?". Its state rides on the
147
- `x-thread-page-activity` header of the revision poll the shell already makes
148
- every ten seconds — so it costs no extra request, nothing in any page's HTML, and
149
- nothing in any agent's instructions. Its wording is a setting; blank hides it.
150
-
151
- Everything else is the page's.
152
-
153
- ## The home page
154
-
155
- `bb thread-page home` designates a thread and, if that thread has no page yet,
156
- writes a session hub. The default groups sessions by project and gives each group
157
- its own look.
158
-
159
- The design point is that **a group is not a project**. A group is a label, a
160
- look, and a *set* of project ids, kept in the page's own scoped storage. One
161
- group per project is only the default; a project may appear in several groups,
162
- and regrouping is an edit to the page rather than a schema change.
163
-
164
- Per-group looks need no second document. The stylesheet's `data-world` attribute
165
- re-resolves every design token for a subtree, so a group can carry a different
166
- palette, typeface, shape language and button style inside one page. Separate
167
- linked pages remain possible through `threads.openPage`; they were not necessary.
168
-
169
- ## The design system
170
-
171
- Five worlds — `paper`, `terminal`, `atrium`, `volume`, `bloom` — each declaring
172
- both light and dark palettes at once, with one resolver publishing the live half
173
- onto the tokens the rest of the sheet uses. A page picks one with `data-theme` on
174
- `<html>`, plus `data-mode` and `data-atmos`.
175
-
176
- It needs no class names: every rule keys off semantic structure, so plain HTML is
177
- already styled. Three class names exist for things structure cannot express —
178
- `.card`, `.needs-you`, `.label`.
179
-
180
- **The stylesheet travels inside each page** rather than being injected at render
181
- time. That means an agent can change any rule for one page, and a plugin update
182
- can never restyle a page the user has already read. The cost is that improvements
183
- to the design system only reach pages created afterwards; that trade favours the
184
- reader.
185
-
186
- A page may add one more `<style>` with two rules: everything inside
187
- `@scope (main)`, and colour and shape from `var(--token)` only. The second is
188
- what keeps a bespoke chart correct in all five worlds and in dark mode.
189
-
190
- ## Instructions
191
-
192
- Three, and only the first is loaded per session.
193
-
194
- 1. **The contract** — why the page matters, how to ask well, what belongs on a
195
- page. Injected into eligible root threads. It holds only what changes
196
- behaviour; every mechanical convention is absorbed by the runtime or the seed.
197
- 2. **The seed comment** — the class names, the theme attributes, the escape-hatch
198
- rule. Free, because the agent is already reading the file.
199
- 3. **The guide** — `bb thread-page guide`. Unbounded, and paid for only by the
200
- sessions that open it.
201
-
202
- There is no skill and no agent tool. Both would put the plugin in every session's
203
- context whether or not the task needs it.
204
-
205
- ## Eligibility
206
-
207
- Only threads a user started get a page. Children, forks and hidden workers get
208
- `SKIP` and answer in chat, because they are not the ones being talked to. `init`
209
- rechecks this at call time rather than trusting the injected instruction.
210
-
211
- ## Testing
212
-
213
- 93 tests over three suites, none needing a browser:
214
-
215
- - `bridge.test.ts` — the capability contract in isolation: validators, limits,
216
- effect classes, confirmation binding. No SDK, DOM, or filesystem.
217
- - `page.test.ts` — HTML parsing against malformed and adversarial documents,
218
- kernel ordering, sandbox invariants, form and label derivation.
219
- - `server.test.ts` — routes, tokens, capabilities, uploads, assets, home, and the
220
- working state, against a fake host.
221
-
222
- Browser verification is done by hand for the things tests cannot see. Several
223
- real defects were found only that way — an invalid CSP source that silently
224
- blocked every relative asset, an auth rule that rejected raw upload bodies, a
225
- `<select>`'s options leaking into an answer label, and a snapshot flag that
226
- selected archived threads instead of adding them. Each has a test now.
227
-
228
- The gap worth naming: there is no hostile-page corpus yet. The confirmation flow
229
- is tested against forgery, replay and parameter-swapping, but no test plays an
230
- attacker trying to reach the parent frame or steal a cookie.
@@ -1,83 +0,0 @@
1
- # Thread Pages
2
-
3
- Give every bb thread its own web page — written by the agent, for that one task.
4
-
5
- ## What it does
6
-
7
- An agent working on a task usually has more to tell you than chat can carry: a
8
- comparison, a diagram, a set of choices, a thing only you can decide. Thread
9
- Pages gives it a real page to say it on.
10
-
11
- The agent runs one command, gets an HTML file, and edits it directly. Saving the
12
- file publishes it. You open one stable link — in bb, in a browser, on your
13
- phone — read the page, and answer from inside it. Your answer arrives as the
14
- agent's next message.
15
-
16
- The page is a complete HTML document the agent writes for the task at hand. Not
17
- a template with slots. If the task needs a chart, it writes a chart. If it needs
18
- an eight-screen wizard, a diagram you click, or three separate forms, it writes
19
- that. The plugin supplies the secure host, never the design.
20
-
21
- ## Why it is built this way
22
-
23
- **Nothing to learn, nothing to load.** There is no skill in the agent's context,
24
- no page tool, no publish protocol, no runtime copied into your files. The
25
- standing instruction is two sentences. Deeper guidance is one optional command
26
- away, so a simple task never pays for it.
27
-
28
- **The page is a file.** It lives in the thread's own storage as ordinary HTML.
29
- The agent reads and writes it with the tools it already has. You can open it,
30
- diff it, or keep it.
31
-
32
- **Your bb, your page.** Pages are served through the bb origin you are already
33
- authenticated to, so they work over bb Connect and on mobile without exposing a
34
- second port or a public URL.
35
-
36
- **Safe by construction.** Page code runs in an opaque-origin sandbox with no bb
37
- cookie, no mutation token, no parent DOM, no raw API, and no general network
38
- access. Anything the page can ask bb to do goes through one narrow, validated
39
- capability at a time.
40
-
41
- ## In the page
42
-
43
- - Any HTML, CSS, and JavaScript the task needs, including Web Components, SVG,
44
- canvas, and multi-screen state.
45
- - Forms that reply to the thread with no code at all — blank answers included,
46
- several forms at once, each with its own state.
47
- - File attachments, stored beside the thread and handed to the agent by path.
48
- - Images, stylesheets, fonts, and data from a confined per-thread asset folder.
49
- - Live thread activity, and a working indicator while the agent is mid-turn.
50
- - Listing, messaging, starting, stopping and archiving sessions from a page.
51
- - Update protection, so a page reload never eats what you were typing.
52
- - A read-only cached copy when the source machine goes offline.
53
-
54
- ## Getting started
55
-
56
- Install the plugin, then turn on **Agent initialization hint** in its settings
57
- to have new sessions use their page automatically. Or leave it off and ask any
58
- agent to run `bb thread-page init`.
59
-
60
- The page seed and the instruction text are both settings, so you can change what
61
- every future page starts from without touching any existing page.
62
-
63
- ## The home page
64
-
65
- `bb thread-page home` writes a session hub grouped by project, each group with
66
- its own look, and every other page then shows a **← Sessions** link back to it.
67
-
68
- Home is an ordinary page afterwards — ask the agent that owns it to regroup or
69
- restyle it. Groups are not tied to projects: a group is a label, a look, and a
70
- set of projects, so "Work" and "Side projects" are as valid as one-per-project,
71
- and a project can appear in both.
72
-
73
- ## Current state
74
-
75
- Everything needed for daily use is implemented and verified in a browser: page
76
- authoring, forms, attachments, confined assets, live activity, the session hub,
77
- and the full capability set with trusted confirmations. Voice dictation has a
78
- defined contract but no handler yet.
79
-
80
- One limitation worth stating plainly: page JavaScript can navigate its own frame
81
- and put data in that URL. Browsers cannot prevent this while still allowing page
82
- scripts. It grants no bb authority, but a page you did not write is still code
83
- you are choosing to run.
package/authoring.ts DELETED
@@ -1,368 +0,0 @@
1
- import {
2
- DEFAULT_HOME_BODY,
3
- DEFAULT_HOME_SCRIPT,
4
- DEFAULT_HOME_STYLE,
5
- } from "./home.js";
6
- import { escapeHtml } from "./page.js";
7
- import { THEME_CSS } from "./theme.js";
8
-
9
- /**
10
- * The standing contract, injected into eligible root threads.
11
- *
12
- * It carries why the page matters and how to ask well, because those are what
13
- * actually change an agent's behaviour. Everything mechanical - form wiring,
14
- * styling, labels, blank handling - is absorbed by the runtime and the seed, so
15
- * none of it is spent here. Anything deeper is one guide command away, paid for
16
- * only by the sessions that need it.
17
- */
18
- export const DEFAULT_AGENT_INSTRUCTION = `# The page is the conversation
19
-
20
- The user does not read chat. Every turn you write or update one HTML page, they
21
- read it and reply from inside it, and their answer arrives as your next message.
22
- Everything they need must be on that page, and every action they might take must
23
- be possible from it — including the ones you would rather they did not choose. A
24
- page they cannot answer from is a dead end. Chat carries the link and one line.
25
-
26
- Start every turn with \`bb thread-page init\`. It prints the page path and link.
27
- Read an existing page before editing it; saving publishes it immediately and an
28
- open page reloads itself. Update it on every turn, including small ones. If init
29
- says SKIP this thread is a helper — answer in chat and stay off the page. When
30
- you spawn threads of your own, parent them to yourself so they stay helpers.
31
-
32
- ## Every page ends with a way to answer
33
-
34
- Any <form> is wired automatically: answers arrive as your next message. Write
35
- plain semantic HTML — it is already styled, and there is nothing to remember.
36
- <fieldset><legend> names a group, a wrapping <label> names one control, <small>
37
- is a hint, and several <button name value> give one-click answers.
38
-
39
- Asking well is most of the work. Answering should cost a click, not a paragraph:
40
- buttons and radios for decisions, checkboxes for multi-select, free text only
41
- where the answer is genuinely open. A range needs a scale that means something
42
- and is easier to drag than to type — never a vague 1-to-5. Nothing is ever
43
- required and blank is a real answer, so ask for everything that would help and
44
- let them skip the rest. Always leave one open text field for what you failed to
45
- anticipate: your form is their only way to redirect you, and a form that permits
46
- only the answers you expect quietly takes the decision away from them.
47
-
48
- ## What belongs on the page
49
-
50
- Only what they cannot skip: what you did, at the level they could explain it to
51
- someone else; decisions that are genuinely theirs, with the options and your
52
- recommendation; what only they can supply; anything they should sanity-check
53
- because a wrong assumption of yours would be costly.
54
-
55
- Every word necessary, nothing said twice. Each update leads with what changed and
56
- has to stand alone, because they answer from that version without scrolling back.
57
- Report failures, skipped steps and your own mistakes plainly. Conclusion first,
58
- detail only if it changes what they do.
59
-
60
- You own the work end to end: make the routine calls yourself, keep every file you
61
- touch correct as you go, and escalate to the page rather than to chat.
62
-
63
- ## The home page
64
-
65
- One thread's page is the home page, and every other page shows a Sessions link
66
- back to it automatically — you never write that link yourself. Home is an
67
- ordinary page: it should list the user's sessions with the threads.snapshot
68
- capability and let them open, continue, or start one.
69
-
70
- If the user asks for a home page, or asks where their sessions are, run
71
- \`bb thread-page home\` in the thread that should own it and then build that page
72
- against \`bb thread-page guide\`. Check whether one already exists before making
73
- a second.
74
-
75
- A page needing more than prose and a form — a chart, a branch, cards to swipe, a
76
- file, live session control — runs \`bb thread-page guide\` first.`;
77
-
78
- /**
79
- * The default new-page document.
80
- *
81
- * The design system travels in the page rather than being injected at render
82
- * time, so the agent can read and change every rule, and a later plugin update
83
- * never restyles a page the user has already read. Plain semantic HTML inside
84
- * <main> is fully styled by it; there are three class names in total.
85
- */
86
- export const DEFAULT_PAGE_SEED = `<!doctype html>
87
- <html lang="en" data-theme="volume" data-mode="system" data-atmos="on">
88
- <head>
89
- <meta charset="utf-8">
90
- <meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
91
- <title>{{TITLE}}</title>
92
- <style>${THEME_CSS}</style>
93
- </head>
94
- <body>
95
- <div class="atmosphere" aria-hidden="true"></div>
96
- <div class="wrap">
97
-
98
- <header class="brief-head">
99
- <h1>{{TITLE}}</h1>
100
- <p class="brief-meta"><span>{{DATE}}</span></p>
101
- </header>
102
-
103
- <!--
104
- Write inside the main element below. Plain semantic HTML is already styled:
105
- h2, p, ul, table, form, fieldset/legend, a wrapping label, small, details.
106
- Three class names exist: .card boxes an aside, .needs-you flags a block
107
- that is blocked on the reader, .label is a small uppercase tag.
108
-
109
- data-theme is paper | terminal | atrium | volume | bloom.
110
- data-mode is system | light | dark. data-atmos is on | off.
111
-
112
- Any extra CSS goes in one more style block, everything inside
113
- @scope (main), and colour and shape from var(--token) only — never a hex.
114
- That is what keeps a bespoke page correct in all five worlds and in dark.
115
-
116
- For charts, multi-screen flows, files, activity, or bridge methods:
117
- bb thread-page guide
118
- -->
119
- <main>
120
- <p>Replace this with what changed and what you need from the reader.</p>
121
-
122
- <form data-title="{{TITLE}}">
123
- <label>Reply
124
- <textarea name="reply" rows="4"></textarea>
125
- </label>
126
- <button name="action" value="Reply">Reply</button>
127
- </form>
128
- </main>
129
-
130
- </div>
131
- </body>
132
- </html>
133
- `;
134
-
135
- /**
136
- * The home page document: the same shell and stylesheet as any other page, with
137
- * a session hub already written into it. It is an ordinary page afterwards, so
138
- * the owning agent can restyle or regroup it on request.
139
- */
140
- export function renderHomeSeed(template: string, now: Date = new Date()): string {
141
- const base = renderPageSeed(template, "Sessions", now);
142
- const bodyStart = base.indexOf(" <header class=\"brief-head\">");
143
- const bodyEnd = base.indexOf(" </div>\n</body>");
144
- if (bodyStart < 0 || bodyEnd < 0 || bodyEnd <= bodyStart) {
145
- // A custom seed we cannot splice: keep the user's own document rather than
146
- // silently replacing it, and let the agent write the hub itself.
147
- return base;
148
- }
149
- return (
150
- base.slice(0, bodyStart) +
151
- DEFAULT_HOME_BODY +
152
- `\n <style>${DEFAULT_HOME_STYLE}</style>\n` +
153
- ` <script>${DEFAULT_HOME_SCRIPT}</script>\n\n` +
154
- base.slice(bodyEnd)
155
- );
156
- }
157
-
158
- export function renderPageSeed(
159
- template: string,
160
- title: string,
161
- now: Date = new Date(),
162
- ): string {
163
- const date = now.toLocaleDateString("en-GB", {
164
- day: "numeric",
165
- month: "long",
166
- year: "numeric",
167
- });
168
- return template
169
- .replaceAll("{{TITLE}}", escapeHtml(title))
170
- .replaceAll("{{DATE}}", escapeHtml(date));
171
- }
172
-
173
- export const AUTHORING_GUIDE = `# Thread Pages authoring guide
174
-
175
- Use the smallest page shape that makes the task easier. Plain semantic HTML is
176
- the default; a Thread Page may also be a complete HTML/CSS/JavaScript mini-app.
177
- Saving the file publishes it.
178
-
179
- ## Built in
180
-
181
- - Every non-manual <form> replies to this thread. Add
182
- data-thread-page-manual when your application owns submission.
183
- - Blank answers are valid. Fieldset legends and labels become answer names.
184
- - Multiple forms have independent pending and dirty state.
185
- - A clicked submit button leads the message as Action.
186
- - window.threadPage.setDirty(true|false) protects custom application state
187
- from an automatic page reload.
188
- - window.threadPage.invoke(method, params) calls an enabled, validated BB
189
- capability. Run context.get to discover the current capability roster.
190
-
191
- ## What plain HTML already gives you
192
-
193
- The seed carries the design system, so semantic HTML is already styled. You do
194
- not need most of what follows; reach past prose only when the shape of the thing
195
- genuinely is not prose.
196
-
197
- h2, p, ul, table the page's type scale, rhythm, rules, tabular figures
198
- form a panel, wiring to this thread, a status line
199
- fieldset + legend a named group; the legend becomes the question
200
- label wrapping one the label becomes that answer's name
201
- small in a label a hint under the option
202
- input type=range a slider with a live value readout
203
- input type=file uploaded on submit, path sent to this thread
204
- details/summary detail on demand; add name="x" for an accordion
205
- div class=card a boxed aside
206
- p class=needs-you a flagged block, for what is blocked on the reader
207
- span class=label a small uppercase tag
208
-
209
- Three class names. That is the whole vocabulary; everything else is selected by
210
- what the element is.
211
-
212
- ## The look is three attributes
213
-
214
- On <html>:
215
-
216
- data-theme paper | terminal | atrium | volume | bloom
217
- data-mode system | light | dark
218
- data-atmos on | off
219
-
220
- Each world sets a palette (both halves at once), a typeface, a shape language,
221
- an atmosphere layer, and its own idea of what choosing and committing look like.
222
- Changing the attribute reskins everything, including anything you built.
223
-
224
- ## The escape hatch
225
-
226
- A page may carry one extra <style> block with two rules:
227
-
228
- 1. Everything inside @scope (main). The browser enforces it, so a page cannot
229
- reach the shell.
230
- 2. Tokens only. No hex, no rgb(). Colour and shape come from var(--...).
231
-
232
- Rule 2 is what keeps a bespoke page inside the system: dark mode still works and
233
- switching world reskins your chart too. A page that writes #3b82f6 is wrong half
234
- the time and nobody notices until night.
235
-
236
- Tokens: --bg --surface --ink --ink-2 --ink-3 --rule --rule-soft --code-bg
237
- --accent --accent-soft --accent-line --flag --ok --font-body --font-head
238
- --radius --rule-w --shadow --measure --space --size --h1-size --label-case
239
- --caps-track --dur --ease
240
-
241
- ## Prefer native HTML first
242
-
243
- - details/summary (and details name="x") for disclosure and accordions.
244
- - input type="range" for an eyeballed scale; Thread Pages adds a live output.
245
- - CSS :has() for simple branches — real different content, not a hidden field.
246
- - overflow-x:auto plus scroll-snap for swipeable cards: a real swipe on a
247
- phone, a scrollbar on a desktop, arrow keys on a keyboard, in four lines.
248
- - inline SVG for diagrams and charts; var(--accent) works inside it. Give a
249
- zero a visible stub bar or the eye reads it as missing data.
250
- - animation-timeline: view() for scroll-linked motion, wrapped in
251
- @media (prefers-reduced-motion: no-preference) so still is the default.
252
- - @starting-style with transition-behavior: allow-discrete for enter/exit.
253
- - dialog, popover, container queries, color-mix(), and view transitions when
254
- they clarify the task.
255
- - Respect prefers-reduced-motion and keep every action keyboard reachable.
256
- Never make something reachable only by pointer.
257
-
258
- ## Before you save
259
-
260
- grep -o '#[0-9a-fA-F]\{3,8\}' page.html # inside your <style>: empty
261
- grep -c '@scope (main)' page.html # 1 if you added a <style>
262
-
263
- Then read it once at 320px wide, once in dark, once with reduced motion. Those
264
- three are where a page that looks finished stops being one.
265
-
266
- ## Complete custom applications
267
-
268
- Inline CSS and JavaScript, Web Components, SVG/canvas, internal routes, and
269
- multi-step state are allowed inside the opaque sandbox. The page cannot read BB
270
- cookies, the mutation token, parent DOM, localStorage, raw SDK/API, CLI, or
271
- arbitrary files. Ordinary fetch and subresource networking are blocked unless a
272
- confined resource is explicitly supplied.
273
-
274
- Arbitrary JavaScript can still navigate its own sandboxed frame and encode
275
- page/input data in the URL. The open mini-app model trusts authored code with
276
- data already visible in its frame. Strong no-exfiltration requires a
277
- declarative/no-authored-JavaScript page.
278
-
279
- ## The session hub, and the home page
280
-
281
- One page is the home page; \`bb thread-page home\` designates the current
282
- thread's. Every other page then shows a Sessions link back to it as chrome, so
283
- no page writes that link. Home is an ordinary page — give it whatever design
284
- suits, and render the list yourself:
285
-
286
- const { threads } = await window.threadPage.invoke("threads.snapshot", { limit: 50 });
287
- // each: id, title, projectId, parentThreadId, status, archived,
288
- // page: { available, revision }, updatedAtMs
289
-
290
- await window.threadPage.invoke("threads.openPage", { threadId }); // its page
291
- await window.threadPage.invoke("threads.openBb", { threadId }); // in bb
292
- await window.threadPage.invoke("threads.continue", { threadId, prompt });
293
- await window.threadPage.invoke("threads.spawn", { projectId, prompt });
294
- await window.threadPage.invoke("threads.archive", { threadId });
295
- await window.threadPage.invoke("threads.stop", { threadId });
296
-
297
- const { projects } = await window.threadPage.invoke("projects.list", {});
298
- const { providers } = await window.threadPage.invoke("providers.list");
299
-
300
- // Folder picker, then create a project from the opaque selection token.
301
- const { selection } = await window.threadPage.invoke("projects.browse", {});
302
- if (selection) await window.threadPage.invoke("projects.create",
303
- { selectionToken: selection.token, name: "My project" });
304
-
305
- await window.threadPage.invoke("navigation.openExternal", { url, label });
306
-
307
- // Small state that survives a reload, scoped to this page.
308
- await window.threadPage.invoke("storage.set", { key: "wizard.step", value: 3 });
309
- const state = await window.threadPage.invoke("storage.get", { key: "wizard.step" });
310
-
311
- Anything that changes another thread, archives, stops, creates a project, or
312
- leaves bb shows a confirmation in trusted chrome first. You do not build that
313
- and cannot word it; a declined action rejects with code "cancelled". Handle it.
314
-
315
- ## Current bridge
316
-
317
- const context = await window.threadPage.invoke("context.get");
318
- const stop = window.threadPage.watch(
319
- "thread.activity",
320
- { limit: 8 },
321
- (value) => renderActivity(value),
322
- { intervalMs: 8000 }
323
- );
324
-
325
- await window.threadPage.invoke("thread.reply", {
326
- title: "Diagram result",
327
- mode: "queue", // or "steer"
328
- result: { selectedNodes: ["a", "b"] },
329
- idempotencyKey: "optional-stable-key"
330
- });
331
-
332
- Call stop() when a watched component unmounts. A page that never calls watch
333
- does no bridge polling.
334
-
335
- ## Files the user sends you
336
-
337
- An automatic form may contain input type="file" (including multiple). On submit
338
- the bytes are uploaded first, stored under this thread's confined upload
339
- directory, and reported to you in the form message as:
340
-
341
- $BB_THREAD_STORAGE/thread-page-uploads/<generated-name>
342
-
343
- Read them there with your normal tools. Each file must be under 24 MiB. Names
344
- are generated by the plugin, so a hostile page cannot choose a path. Uploads
345
- fail visibly on the page; they are never silently dropped.
346
-
347
- ## Files you show the user
348
-
349
- Put sibling resources in:
350
-
351
- $BB_THREAD_STORAGE/thread-page-assets/
352
-
353
- Reference them relatively (<img src="chart.png">, <link href="page.css">) or
354
- resolve one explicitly:
355
-
356
- const url = window.threadPage.assetUrl("chart.png");
357
-
358
- The directory is exposed to the page as one temporary, path-shaped preview and
359
- is the only network origin the page's CSP allows. Names may use letters,
360
- digits, dot, dash, and underscore only, with no subdirectories. When the
361
- directory does not exist there is no asset base and assetUrl throws.
362
-
363
- ## Design ownership
364
-
365
- The plugin does not impose a theme or component library. You may define any
366
- task-specific visual system. Prefer CSS custom properties with light/dark
367
- values so the page stays coherent, and test at a narrow mobile width.
368
- `;