Browser messages and thread transcripts
The replacement dashboard uses the current execution engine through HomeSession. Home shows assigned questions and work context; one composer sends to the explicitly selected destination. Record 0102 replaces the old rail and pinned Plane composer. Native adapter APIs remain compatible; their tests do not prove production activation of the new browser host.
- Code:
web/green/pages/ThreadBrowser.vue,web/green/pages/Thread.vue,web/green/components/ReadonlyThread.vue,web/green/components/ConversationDock.vue,web/green/components/ConversationComposer.vue,web/green/lib/homeSession.ts,web/green/lib/readonlyThread.ts,web/src/components/home/,web/src/lib/runStream.ts,src/channels/web.ts,src/channels/homeRuntime.ts,src/channels/homeSeed.ts,src/channels/webSeed.ts. - Tests:
web/green/pages/discovery.test.ts,web/green/pages/reportStates.test.ts,web/src/pages/home.test.ts,web/green/frontend.test.ts,web/green/pages/homeActions.test.ts,web/green/components/conversationDock.test.ts,web/green/lib/homeSession.test.ts,web/src/lib/runStream.test.ts,src/channels/web.test.ts,src/channels/homeRuntime.test.ts,src/core/authz/channelDirectory.test.ts,src/core/authz/actor.test.ts,src/core/authz/grants.test.ts. - Docs: browser-home.md, live-view.md, authorization.md, thread-admission.md, record 0102.
Behavior
- Home uses one private broad session and one explicit-destination composer. Reading a thread does not select it for a reply. A fresh server proof is required before Reply changes the destination.
- Recorded run turns retain their request, receipt, answer, facts, and shared replay fold. A failed run without a reply shows its recorded outcome. Silent receipts and the parent's recorded words stay in transcript order; neither creates a model run.
- Current source or identity loss clears private transcript, drafts, and pending retries. View as remains read-only. Unknown submission outcomes retain the issued key and immutable payload; a retry checks that saved submission.
- The removed conversation rail, local owner toggles, brand motion, and pinned Plane chat are superseded. Their backend run, thread, and saved-choice APIs remain available to native clients.
Validation criteria
| Criterion | Evidence |
|---|---|
| Recent thread discovery retains history-store and ledger unavailability and both run/thread bounds. A healthy empty recent window, unavailable read and partial loaded list remain distinct. Every list is current-access filtered; no seed claims a full retained-history census. | [unit] src/channels/web.test.ts::GET /threads and /threads/<id> — the seed from the runs service (items 2, 7, 8, 11)::preserves unavailable and partial thread discovery separately from an empty authorized recent window, ::marks the bounded thread rail partial when additional authorized conversations are omitted, web/green/pages/discovery.test.ts::Thread discovery availability::distinguishes healthy empty, unavailable and partial authorized thread windows, web/green/pages/reportStates.test.ts::Green report states::limits thread discovery and filtering to the loaded browser list without implying full history coverage |
| Recorded run turns retain request, receipt, answer, outcome, and one shared replay read. User stops use info colors; failed and interrupted runs keep warning colors. | [unit] web/src/pages/home.test.ts::HomePage — a finished conversation (items 4–6)::* |
| A silent receipt stays between the two recorded run turns | [unit] web/src/pages/home.test.ts::HomePage — silent receipts on the thread view (item 12)::a silent receipt draws as a read-not-answered turn with its reason, in order among the runs |
| The parent's recorded word stays in order and retains its original run link and live token | [unit] web/src/pages/home.test.ts::HomePage — the parent's word on a unit's thread (item 2)::* |
| One composer changes its announced destination only after explicit authorized focus; current revocation removes transcript and editor | [unit] web/green/frontend.test.ts::Green browser frontend::mounts one composer and changes its announced destination only after explicit thread focus, ::removes the composer and transcript on an active private-feed revocation |
| Unknown outcomes keep the original issued key and exact body | [unit] web/green/lib/homeSession.test.ts::Home conversation destination::retries an uncertain response with the original issued key and exact payload |
Retained native browser adapter compatibility
These rows test the retained adapter protocol independently of the active dashboard entry. The new browser admission and identity authority are specified in browser-home.md and organization.md.
| Criterion | Evidence |
|---|---|
7: the rail's seed names a thread's channel when the name directory knows it — channelName beside channelId on a row from another surface, nothing on the viewer's own lane, the id alone when the lookup has no answer or fails | [unit] src/channels/web.test.ts::GET /threads and /threads/<id> — the seed from the runs service (items 2, 7, 8, 11)::the rail names a thread's channel when the name directory knows it (record 0042, the dashboard reads names): channelName beside channelId on the Slack row, nothing on the viewer's own lane, the id alone when unknown or failing |
2: a conversation whose runs carry an instance tag lists the parent's ship_unit events naming this thread in time order among its runs and none naming another thread; a conversation with no tagged run reads no parent, and so does a viewer whose predicate excludes the parent | [unit] src/channels/web.test.ts::GET /threads/<id> — the parent's word on a unit's thread (item 2)::* |
| 2, 12: two silent receipts seed two read-not-answered turns with their reasons, interleaved among the runs by decidedAt, an addressed receipt seeds nothing; zero receipts, a null ledger and a failing one seed the runs alone; a thread the viewer may see nothing of stays the 404 and the ledger is never asked; a receipt turn is skipped by the history a run reads | [unit] src/channels/web.test.ts::GET /threads/<id> — silent intake receipts on the thread view (item 12)::*, src/channels/web.test.ts::the projections — requester, turn, history, threads, title, palette, chips::historyOf: a receipt turn is no one's line — the history a run reads carries the runs alone |
11: the routes — /threads, /threads/<id> (a conversation id or a whole thread key, encoded or not), /threads/<id>/send; anything else under the prefix is not a route; a plain id is the session's own lane, a key with a colon another channel's thread | [unit] src/channels/web.test.ts::parseThreadsRoute — /threads, /threads/<id>, /threads/<id>/send::* |
11: a plain request at /threads/<id> dispatches { userId: access:<sub>, channelId: web:<sub>, threadKey: web:<sub>:<id>, text, receivedAt } and, once the run exists, answers 202 with the run's own view path; a linked session sends as its person with the session as authenticatedAs; a run-less reply is 200 with the text; a run discarded before its token was read is answered as run-less; a run reads the thread's finished turns as its history only when it asks | [unit] src/channels/web.test.ts::POST /threads/<id>/send — the body into dispatch() as this session (item 11)::a plain request dispatches…, ::a linked session sends as its person…, ::a request the pipeline answers without a run…, ::a run gone from the registry before its token was read…, ::a run reads the thread's finished turns as its history… |
| 11: refusals before dispatch — GET 405, a foreign origin 403, a non-JSON body 415, an empty text 400, another channel's thread 403, an oversized body 413 — and dispatch is never reached | [unit] src/channels/web.test.ts::POST /threads/<id>/send — the body into dispatch() as this session (item 11)::refusals before dispatch… |
11 (record 0060): the send's handle can open a thread of its own — openThread mints a conversation in the session's lane, two calls two ids, the child's handle bound to the new key — and resumeWebIO rebuilds a handle from a bare thread key: history() lists the thread's runs as the session's own actor, an out-of-run reply resolves and logs the length never the text, undeliverable set, and a key that is not a web conversation rebuilds nothing | [unit] src/channels/web.test.ts::the web handle rebuilt from a bare thread key — threadIoFor's web: arm (record 0060; web-chat item 11)::*, src/channels/web.test.ts::POST /threads/<id>/send — the body into dispatch() as this session (item 11)::the dispatched handle can open a thread of its own… |
2, 11: /threads mints a conversation of the viewer's own lane with no turns, a send URL under it, the palette the viewer may run and chips grounded in their runs, and carries no token; /threads/<id> seeds exactly the thread's runs the viewer may read, oldest first, each with its request, reply and route, a live run with its token, and the rail lists the viewer's threads across channels, newest first, each with its title and its excerpt, a foreign thread by its whole key with its surface | [unit] src/channels/web.test.ts::GET /threads and /threads/<id> — the seed from the runs service (items 2, 7, 8, 11)::/threads mints a conversation…, ::/threads/<id> seeds exactly the thread's runs… |
11: a stranger's thread, or one the viewer may see nothing of, is the same 404 an unknown run gives; the viewer's own empty conversation opens empty; a thread from another channel the viewer may read opens with elsewhere and refuses a send | [unit] src/channels/web.test.ts::GET /threads and /threads/<id> — the seed from the runs service (items 2, 7, 8, 11)::a stranger's thread…, ::a thread from another channel the viewer may read opens read-only… |
| 2, 8, 11: the pure projections — the requester, the turn, the history, the thread groups, the title cut, the palette by exposure and grant, the chips by the viewer's facts, the IO's on-demand history | [unit] src/channels/web.test.ts::the projections — requester, turn, history, threads, title, palette, chips::* |
| Trusted owner aliases retain their readable threads without lending private source authority; legacy calls keep their original list | [unit] src/channels/web.test.ts::GET /threads and /threads/<id> — the seed from the runs service (items 2, 7, 8, 11)::uses current proved owner scope in the loaded thread list without lending private source read authority |