Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7caf8008b5 | ||
|
|
ac34d97862 |
@@ -0,0 +1,549 @@
|
||||
# Q195 pane and pin: split layout, chat session model, document context
|
||||
|
||||
Fenton (Opus 5.5), 2026-10-21T15:53:19Z, for Vikunja #448 and DECISIONS Q195. Gyro folds this section
|
||||
into `docs/plans/2026-10-21_q195-writing-editor.md`. It is measured against origin/refactor 6c75887b0.
|
||||
Paths are relative to `packages/` unless they start with `docs/`.
|
||||
|
||||
This is design only. It covers the split pane, the chat session that stays on screen, the pin, and what
|
||||
the agent learns about the open document. The thesaurus, document scope and the editor's agent edit
|
||||
path are Gyro's.
|
||||
|
||||
## 1. What exists today (measured)
|
||||
|
||||
M1. The Console chat is built from projects and threads. Both are bus UUIDs. Each thread is bound to one
|
||||
instance, and the bus chooses that instance (`bus/src/broker.mjs:3429-3438`). A user reaches only their
|
||||
own threads (`broker.mjs:451-457`, REQ-USER-2).
|
||||
|
||||
M2. Chat goes through `/api/chat/*` (`webui/src/chat-http.mjs:104-129`), then `webui/src/chat.mjs:20-26`,
|
||||
then the webui channel plugin. The plugin allows exactly seven verbs: `project.list`, `project.create`,
|
||||
`thread.list`, `thread.create`, `thread.messages`, `thread.send` and `thread.open`
|
||||
(`plugins/channel/webui/index.mjs:4-12`). Transport is the human CLI child process
|
||||
(`webui/src/bus.mjs:187-231`).
|
||||
|
||||
M3. Replies arrive by polling every 10 s (`webui/src/public/bus.js:1069`, `:1102-1104`). There is no SSE
|
||||
or WebSocket.
|
||||
|
||||
M4. The chat and the editor both render into `#bus-view`. Every route change destroys the editor
|
||||
(`bus.js:881`, `:1125`, `:1144`), so the two can't be on screen together. There is no splitter. The only
|
||||
resize handles are on textareas (`chat.css:16`, `bus.css:48`).
|
||||
|
||||
M5. The chat route is parsed from the hash (`chat.js:119`). Chat drafts and read cursors live in
|
||||
in-memory Maps (`chat.js:9`, `:13`), and the chat stores nothing in the browser. `app.js` keeps two
|
||||
localStorage keys, `mosaic-console-board` and `mosaic-console-appearance` (`app.js:14`, `:60`).
|
||||
|
||||
M6. The chat sends no page or document context. The send body is `{body}` only (`chat.js:232`), and
|
||||
`chat-http.mjs:112` refuses any other key. `thread.send` accepts only `thread`, `body` and `class`
|
||||
(`broker.mjs:3480`), and `messages` has no context column (`bus/schema.sql:70-82`).
|
||||
|
||||
M7. The editor bundle exports `mount` and `roundTrip` only (`webui/editor/entry.mjs:8`). `mount` returns
|
||||
`{view, text, setText, focus, destroy}` (`editor/view.mjs:161-208`). It has no selection API. The open
|
||||
document is `docKey = [project, path]` with the base commit in `docStates` (`bus.js:874-875`, `:908`). A
|
||||
save is `project.file.save`, which is human-only and refuses a stale base
|
||||
(`broker.mjs:2411-2429`, `project/src/index.mjs:248-261`). Openable paths are `spec/north-star.md` and
|
||||
`docs/<name>.md` only (`project/src/paths.mjs:14-15`).
|
||||
|
||||
M8. There is no "pin" for chat or documents anywhere in the code. Every `pin` match is unrelated: engine
|
||||
version, fork account, SSH host key, DNS, persona digest.
|
||||
|
||||
M9. Case 2 status:
|
||||
- C2-08 (persona block) landed in 361ba2387. Creative tools are `read`, `grep`, `find`, `ls` and `web`
|
||||
(`business/src/vocabulary.mjs:187`).
|
||||
- C2-06 (cited-reply guard), C2-09a (one thread per creative instance) and C2-10b (cited items in the
|
||||
Console chat) are briefed, with no code.
|
||||
- `renderItems` exists in `library/src/render.mjs`, and its cite shape is `{path, commit, lines, quote}`
|
||||
or `{url, quote}`.
|
||||
|
||||
M10. The "sessions" in `git log --grep=session` are mostly SESSIONS.md registry commits. The real session
|
||||
model is harness sessions kept per thread under `threads/<business>/<thread>/`
|
||||
(`seat/src/session.mjs:15-26`, `:59-75`, commit 2c16d6b93). The conversation reader
|
||||
(`conversation/src/reader.mjs:244`, `:400`) is a separate raw-session view. The Console reaches it only
|
||||
through `/api/conversation(s)`, which is proxied to the board on 7331 (`webui/src/serve.mjs:12`,
|
||||
`:589-599`).
|
||||
|
||||
## 2. Layout
|
||||
|
||||
L1. Placement. On `#/docs` and `#/docs/<project>/<path>`, `main` holds three things side by side: the
|
||||
document area (`#bus-view`), a divider, and a new `aside#chat-pane`. The chat pane is a sibling of
|
||||
`#bus-view`, not a child of it. A document change re-renders `#bus-view` only, so the chat pane, its
|
||||
draft and its scroll position survive. Other routes hide the pane. The full `#/chat` route stays as it is.
|
||||
|
||||
L2. Embedded chat. `MosaicChat.render` takes a thread id and a container, and does not read or write
|
||||
`location.hash` in this mode. Today it parses the hash (`chat.js:119`). The pane runs its own 10 s poll
|
||||
while it is visible, and the poll stops when the tab is hidden or signed out.
|
||||
|
||||
L3. Divider. The divider is a focusable element with `role="separator"`, `aria-orientation="vertical"`,
|
||||
`aria-controls="chat-pane"`, and `aria-valuenow`, `aria-valuemin` and `aria-valuemax` set to the chat
|
||||
width in percent. This is the WAI-ARIA window splitter pattern.
|
||||
- Pointer: drag with `setPointerCapture`. A double click resets the split to the default.
|
||||
- Keyboard: Left and Right arrows move the divider 2%, and Shift with an arrow moves it 10%. Home and End
|
||||
go to the minimum and maximum chat width. Enter collapses the chat pane, and Enter again restores it.
|
||||
- Limits: the document area is at least 28rem and the chat pane at least 18rem. The split is clamped
|
||||
again whenever the window is resized.
|
||||
- Default: 62% document and 38% chat.
|
||||
|
||||
L4. Sizes remembered. One localStorage key, `mosaic-console-split`, holds `{chat: <percent>, collapsed:
|
||||
<bool>}`. It is written when a drag or key press ends, not on every move. It is per browser, not per
|
||||
document. That matches the existing `mosaic-console-*` keys (M5). A bad value falls back to the
|
||||
default.
|
||||
|
||||
L5. Narrow screens. Below 60rem the panes stack and a two-tab bar appears ("Document" and "Chat",
|
||||
`role="tablist"`). This matches the existing ≤959px step in `console.css:34-44`. One pane shows at a
|
||||
time, and the divider is hidden. Switching tabs keeps the editor draft and the chat draft. The stored
|
||||
split is kept for the next wide view. A new reply while the Document tab is showing puts a count on the
|
||||
Chat tab. It never switches tabs on its own.
|
||||
|
||||
L6. A chat pane action never touches `docStates`. An unsaved draft stays as it is when the user switches
|
||||
threads, pins or unpins.
|
||||
|
||||
## 2a. Any editor in the left pane (DECISIONS Q196: O1, O2, O3)
|
||||
|
||||
Q196 puts three editors on the table:
|
||||
- O1 extends the row 154 ProseMirror editor.
|
||||
- O2 runs wordcraft's WebAssembly canvas app in the pane.
|
||||
- O3 is a mix.
|
||||
|
||||
The pane, the divider and the pin model don't depend on which one is chosen. The pane talks to the left
|
||||
pane only through the host contract in E1. Pins are keyed by project and path, whatever the file format.
|
||||
|
||||
E1. The host contract:
|
||||
- `mount(host, {project, path})` and `destroy()`.
|
||||
- `dirty()`, which the shell asks before any navigation that unmounts the editor.
|
||||
- `focus()`.
|
||||
- `selection()`, which returns a locator or null (C2, E5).
|
||||
- `resized()`, which the shell calls once a drag settles.
|
||||
|
||||
O1 implements the contract directly from `editor/view.mjs`. O2 implements it through an iframe and a
|
||||
message bridge (E2).
|
||||
|
||||
Measured from wordcraft's web build (`/mnt/storage/src/artcraft/wordcraft`, reference only):
|
||||
- W1. The canvas covers the whole page (`apps/wordcraft-web/index.html:11`). The app adds listeners to
|
||||
`document` and `window`: a picture paste handler (`src/web.rs:170-171`) and a `beforeunload` guard
|
||||
(`src/web.rs:377`, and `guard_unload`).
|
||||
- W2. No function is exported to JavaScript. A search for `#[wasm_bindgen` in `apps/` and `crates/` finds
|
||||
nothing. The selection exists only inside the engine and through its MCP document resource
|
||||
(`crates/mcp/src/server.rs:151`).
|
||||
- W3. The app keeps preferences in localStorage key `wordcraft.ui` (`apps/wordcraft-web/src/prefs.rs:10`).
|
||||
- W4. The ribbon shrinks to fit. Groups go compact, then collapse, and the ribbon scrolls if even that
|
||||
doesn't fit (`crates/ui-egui/src/ribbon_fit.rs:1-13`). The user can also collapse the ribbon
|
||||
(`ribbon.rs:437`). KeyTips take Alt and F10 (`options.rs:517`).
|
||||
- Not measured: which element eframe 0.36's keyboard listeners attach to. eframe's source isn't in the
|
||||
local cargo registry. Louie's evaluation (origin/louie/wordcraft-eval) or the O2 row pins this down.
|
||||
|
||||
E2. O2 runs in an iframe, not in the Console's document. The iframe is served from its own Console
|
||||
route.
|
||||
- W1 then stays inside the iframe. Its document-wide paste and key handling can't catch input meant for
|
||||
the chat pane, and its page-wide canvas fills the iframe instead of the Console.
|
||||
- The iframe route can carry its own CSP (`wasm-unsafe-eval`) while the Console page's CSP stays as it
|
||||
is. That is narrower than loosening the editor's CSP, but it is still a CSP change under Q167 D3 and
|
||||
needs Jason's ruling (J6).
|
||||
- The iframe has the same origin, so `wordcraft.ui` sits beside the `mosaic-console-*` keys with no
|
||||
clash (W3).
|
||||
- Parent and iframe talk by `postMessage` only. The messages are `dirty`, `selection`, `focus-out` and
|
||||
`resized`. The parent checks `event.origin` and `event.source` on every message. That bridge is new
|
||||
wordcraft-side code (W2), which the O2 row carries.
|
||||
|
||||
E3. Focus and shortcuts. A shortcut belongs to the pane that has focus. The shell binds no global
|
||||
editing keys.
|
||||
- In O1, Ctrl+F and Ctrl+K go to the editor when the editor has focus, as row 154 does now. In the chat
|
||||
pane they are the browser's.
|
||||
- In O2, Ctrl+F, Ctrl+K, Ctrl+S, Ctrl+P, Alt and F10 go to wordcraft while the iframe has focus. The
|
||||
browser delivers keys only to the focused document, so this split comes for free.
|
||||
- The shell owns two keys. F6 and Shift+F6 cycle focus between the editor, the divider and the chat pane.
|
||||
Esc in the chat send box returns focus to the editor.
|
||||
- A canvas app can hold F6. So in O2, wordcraft posts `focus-out` on F6, and the shell moves focus to
|
||||
the divider. Without that message, a keyboard user can't leave the editor. The O2 row must test this
|
||||
before anything else.
|
||||
- A click on either pane focuses that pane in all options.
|
||||
|
||||
E4. Resize.
|
||||
- O1 reflows with CSS.
|
||||
- O2 redraws a WebGPU/WebGL surface (Q195 reading, `apps/wordcraft-web/Cargo.toml:3`).
|
||||
- During a drag the shell sets `pointer-events: none` on the iframe, so the drag doesn't stall when the
|
||||
pointer crosses the canvas.
|
||||
- It resizes the iframe once per animation frame and calls `resized()` when the drag ends.
|
||||
- A keyboard step (L3) is a single resize.
|
||||
|
||||
E5. Selection to the agent. C2 sends `lines` because the C2-06 guard and `renderItems` cite by
|
||||
`{path, commit, lines, quote}` (M9).
|
||||
- O1 maps the selection to Markdown source lines (C6).
|
||||
- O2 asks the iframe for `selection`, and only at send time (C1).
|
||||
- On a Markdown file opened through wordcraft's Markdown import and export (Q196 reading, README.md:95,
|
||||
:124), the iframe returns lines of the saved file, and C2 holds unchanged.
|
||||
- On a .docx file, lines don't exist. A cite needs a new locator, such as a paragraph index range at a
|
||||
commit, and that changes C2-06 and `render.mjs`. This section doesn't decide that. It goes to Gyro's
|
||||
format decision and Jason's checklist (J7).
|
||||
- Until then, a .docx document sends `{project, path, commit}` with no `lines`, and the chip says so.
|
||||
|
||||
E6. Narrow screens with a ribbon. A ribbon needs more width than the O1 toolbar.
|
||||
- The editor's minimum width is 28rem for O1. For O2, and for O3 when it has a ribbon, it is 44rem.
|
||||
- The panes stack into tabs (L5) below 60rem for O1, and below 76rem for O2 and O3 with a ribbon. 76rem
|
||||
is 44rem plus the 18rem chat plus the nav tree and the divider.
|
||||
- Between the stack point and the sum of the minimums, the shell collapses the chat pane to a 2.5rem
|
||||
rail with an unread count. It doesn't squeeze the ribbon.
|
||||
- In the stacked layout the ribbon gets the full width, and wordcraft's own fit and collapse (W4)
|
||||
handle anything narrower.
|
||||
|
||||
E7. Unsaved work on navigation. Removing an iframe fires no `beforeunload` in it. A hash route change
|
||||
doesn't fire one either. So before every unmount, in every option, the shell asks the editor for
|
||||
`dirty()` and asks the user before it navigates away. In O2 that is a `postMessage` round trip with a
|
||||
1 s timeout. A timeout counts as dirty.
|
||||
|
||||
## 3. Session model and pin
|
||||
|
||||
S1. What a session is. In the pane, a session is a bus thread (M1). A pin names a thread UUID and nothing
|
||||
else. It never names a harness session file or a conversation-reader catalogue id. Melanie's chat goes
|
||||
through `/api/chat` and the bus thread verbs. Her replies render through C2-10b's `renderItems`, never
|
||||
through `/api/conversation(s)`. So C2-10a (row 220) hiding creative sessions from `catalogue()` and
|
||||
`open()` does not affect the pane. The pane doesn't use the 7331 board route that row 198 retires.
|
||||
|
||||
S2. Two states:
|
||||
- **Follows you (unpinned).** The pane shows the user's current thread, and it stays through every
|
||||
document change. The current thread is kept in localStorage `mosaic-console-chat` as `{thread}`. The
|
||||
key is cleared on sign-out, as the chat's Maps are (`chat.js:23-27`).
|
||||
- **Pinned.** The pin binds the pair (owner, project, path) to a thread. Opening that document switches
|
||||
the pane to the pinned thread. Leaving it for a document without a pin brings back the follow-you
|
||||
thread as it was. The pane header always says which state it is in: "Pinned to docs/ch-01.md" or
|
||||
"Follows you".
|
||||
|
||||
S3. Rules:
|
||||
- A document has at most one pin per owner.
|
||||
- A thread may be pinned to several documents, for example a chapter set.
|
||||
- Pinning while a document is open pins the thread currently shown.
|
||||
- Unpinning leaves the thread as the follow-you thread.
|
||||
- When the user switches threads in the pane while a pinned document is open, the pane asks one thing:
|
||||
"Pin this thread here instead?" It doesn't re-pin silently.
|
||||
|
||||
S4. Two documents pinned to different threads. Document A is pinned to thread T1 and document B to T2.
|
||||
Going from A to B swaps the pane from T1 to T2. Each thread keeps its own draft and cursor (the per-thread
|
||||
Maps, `chat.js:9`, `:13`). Polling covers the shown thread only. A reply in T1 while T2 is on screen shows as an unread count on
|
||||
the pins list, not as a pane switch. The count comes from unread metadata that `thread.pins` returns, not
|
||||
from polling every pinned thread's messages.
|
||||
|
||||
S5. A pinned document that was moved or deleted. Pins are keyed by path. The editor never renames
|
||||
(M7), so a move happens in git outside the Console. Pins don't follow git rename detection, because a
|
||||
heuristic rename match could tie a thread to the wrong chapter. When the pinned path is missing at the
|
||||
project's HEAD, the pin is stale:
|
||||
- The pins list shows it as "not found at <short commit>", with two actions, "Re-pin to..." and "Unpin".
|
||||
- A stale pin is never deleted on its own, and nothing else changes.
|
||||
- A pin whose thread is gone or no longer the owner's (`thread-not-found`, `thread-not-owned`) shows the
|
||||
same way, with "Unpin" only.
|
||||
|
||||
S6. Where pin state lives. **Decision: the bus**, in a new table:
|
||||
`thread_pins(business, owner, project, path, thread, at)`, unique on `(business, owner, project, path)`.
|
||||
|
||||
| Option | For | Against |
|
||||
|---|---|---|
|
||||
| Browser (localStorage) | No schema change, fastest to build | Lost on another machine or browser. Melanie works from her own machine, and the pin would fork per device |
|
||||
| **Bus** | Follows the user everywhere. `#thread` already checks the owner, so a pin can't expose another user's thread. The bus can validate the path with `documentPath` | A schema migration and three new verbs |
|
||||
| Project repo (a file) | Lives with the document | Writes chat metadata into her writing repo as commits she didn't make. A shared repo would mix users' pins |
|
||||
|
||||
The verbs are `thread.pin {thread, project, path}`, `thread.unpin {project, path}` and `thread.pins
|
||||
{project?}`. All three are human or tester only, as `thread.send` is (`broker.mjs:3479`). `thread.pin`
|
||||
runs `#thread` (owner check) and `documentPath`. `thread.pins` reports whether each path exists at HEAD.
|
||||
The verbs go on the webui channel allowlist (M2) and the `/api/chat` routes. Only the follow-you thread
|
||||
and the split stay in the browser. Both are per device by nature.
|
||||
|
||||
## 4. What the agent learns about the open document, and when
|
||||
|
||||
C1. When. Only when the user sends a message. Nothing goes on cursor moves, selection changes, document
|
||||
opens, or polls. The agent learns what the user chose to send, at the moment it is sent.
|
||||
|
||||
C2. What. The send carries an optional `context`:
|
||||
`{project, path, commit, lines: [from, to]}`.
|
||||
- `commit` is the base commit of the open document (`docStates`, M7).
|
||||
- `lines` is the selection as 1-based lines of that commit's text.
|
||||
- `lines` is present only when the selection is non-empty and the draft is clean. With unsaved edits,
|
||||
line numbers don't map to any commit. In that case the context carries the path and commit only, and
|
||||
the chip says "Selection not sent: save first so the assistant can cite it."
|
||||
|
||||
C3. No document text goes in the context. The agent reads the lines itself with its `read` tool, which
|
||||
creative roles already hold (M9). So her prose is not copied into `messages`, and the agent's reply can
|
||||
cite exactly `{path, commit, lines, quote}`, the shape `renderItems` and the C2-06 guard check (M9).
|
||||
|
||||
C4. Consent is visible. Above the send box, a chip shows what will be sent, for example
|
||||
"docs/ch-01.md · lines 12-14". One click removes it for that message. With no document open, there is no
|
||||
chip.
|
||||
|
||||
C5. Storage and delivery. `thread.send` takes the optional `context`. The broker validates it before the
|
||||
insert:
|
||||
- `project` must be an existing project of the business.
|
||||
- `path` must be a path the editor may open for that project: `documentPath`, or the library's mapped
|
||||
paths on a library business. `documentPath` itself is not widened (gyro-0009).
|
||||
- `commit` must be 40-hex and reachable from the project's HEAD, and `path` must exist at it.
|
||||
- `lines` must be two positive integers with `from ≤ to ≤` the file's line count at that commit.
|
||||
- Any extra key is refused with `invalid-context`.
|
||||
|
||||
The context is stored in a new table, `message_context(message, project, path, commit, line_from,
|
||||
line_to)`, in the same migration as `thread_pins`. `message.read` and `inbox` (`broker.mjs:3050`,
|
||||
`:3245`) return it as a separate `context` field, never merged into `body`.
|
||||
|
||||
C6. Editor support. The editor gains `selection()`, which returns the Markdown source lines covered by
|
||||
the ProseMirror selection. It uses markdown-it token `map` line ranges and returns null when no clean
|
||||
mapping exists. The editor package changes nowhere else.
|
||||
|
||||
## 5. Case 2 guardrails hold
|
||||
|
||||
G1. C2-06, the cited-reply guard. The context is human-to-agent data. The guard checks agent-to-human
|
||||
messages before the insert (case2-rows.md:385-389), so the pane adds no path around it. The pane renders
|
||||
replies through C2-10b only. Nothing in this section lets an agent change the document. Any agent edit
|
||||
or replacement proposal is in Gyro's section, gated on Jason's explicit choice under Q176.
|
||||
|
||||
G2. C2-08, the persona block. Context never enters the bundle, the system prompt or the persona file. It
|
||||
travels as message data in a field of its own. Because `path` must pass `documentPath`, it can't carry a
|
||||
newline or a `# ` heading, so it can't fake a prompt section.
|
||||
|
||||
G3. C2-09a, one thread per creative instance. The pane's "New thread" uses C2-09a's instance picker. A
|
||||
pin binds a thread, so it binds that thread's instance with it. Bean and Chet threads can be pinned to
|
||||
different chapters (S4). The pane never picks an instance itself.
|
||||
|
||||
G4. M7 and M8 of C2-10a (row 220) are unaffected (S1).
|
||||
|
||||
## 6. Dependencies and points for Jason's checklist
|
||||
|
||||
J1. A schema change: one migration adding `thread_pins` and `message_context`, plus four verb changes
|
||||
(`thread.pin`, `thread.unpin`, `thread.pins`, and `context` on `thread.send`). Under AGENTS.md, a new
|
||||
schema or API needs Jason's approval. The fallback without a schema change keeps pins in localStorage,
|
||||
loses them across devices, and sends no structured context.
|
||||
|
||||
J2. Pin key. The decision is per owner, by path, with no rename following (S5).
|
||||
|
||||
J3. Context. The decision is send-time only, with a visible chip and line numbers but no text (C1-C4).
|
||||
|
||||
J4. Document scope. The pane works on any path the editor opens. Today that is `docs/*.md` and the spec
|
||||
only (M7). Melanie's chapters need Gyro's document scope change first.
|
||||
|
||||
J5. This design does not depend on the row 5 brief (`docs/plans/2026-09-13_webui-session-chat.md`,
|
||||
#1507), which is waiting on Jason. If Jason's row 5 ruling changes the chat surface, rows P1 and P3 are
|
||||
re-measured.
|
||||
|
||||
J6. O2 or O3 with wordcraft: a separate CSP for the iframe route (E2). Q167 D3 bars loosening the
|
||||
editor's CSP, so this needs Jason's ruling.
|
||||
|
||||
J7. O2 on .docx: a cite locator that isn't line-based (E5). That changes C2-06 and `render.mjs`. It
|
||||
waits on Gyro's format decision and Jason's ruling.
|
||||
|
||||
## 7. Cross-row findings
|
||||
|
||||
X1. C2-10b's "Files owned" names `webui/src/public/bus.js`, but thread messages render in
|
||||
`chat.js:68` as plain `txt(m.body)`. C2-10b needs `chat.js` in its file list, or the cited reply won't
|
||||
reach either the chat route or the pane. This goes to Sage for row 225.
|
||||
|
||||
X2. C2-09a's brief cites `broker.mjs:2331-2334` and `:290-291`. At 6c75887b0 the code is at
|
||||
`:3429-3438` and `:385-386`. The row has to re-measure before building.
|
||||
|
||||
## 8. Rows
|
||||
|
||||
The order is P2 → (P1 ∥ P4) → P3. P5 is added only if Jason picks O2, or O3 with wordcraft. P1 builds
|
||||
the host contract (E1) and the O1 adapter whatever the choice, so P5 plugs into it. Merge-chaining applies (Q177): each row builds on the previous row's
|
||||
candidate and gets its own review.
|
||||
|
||||
## P1 Q195 split pane: editor and chat side by side with a keyboard divider
|
||||
|
||||
### Problem
|
||||
|
||||
The chat and the editor share `#bus-view`, and each navigation destroys the editor (`bus.js:881`,
|
||||
`:1125`), so they can't be on screen together. There is no splitter, and `MosaicChat.render` takes its
|
||||
thread from the hash (`chat.js:119`).
|
||||
|
||||
### Owner and reviewer
|
||||
|
||||
- Owner: a Haiku 5.5 subagent at high effort (mid-level UI), dispatched by Sage.
|
||||
- Reviewer: an Opus 5.5 seat that is not the author (Fenton).
|
||||
- After: C2-10b (row 225), so that replies in the pane render as cited items.
|
||||
|
||||
### Files owned
|
||||
|
||||
`packages/webui/src/public/index.html`, `bus.js` (the docs routes only), `chat.js` (the embedded mode
|
||||
only), a new `pane.js` and `pane.css`, and tests in `packages/webui/tests`.
|
||||
|
||||
### What ships
|
||||
|
||||
Design sections L1 to L6, and E1, E3, E4 and E7 for O1. Tests:
|
||||
- The divider's ARIA attributes and keys: arrows, Shift with arrows, Home, End and Enter.
|
||||
- The clamps at 28rem and 18rem.
|
||||
- `mosaic-console-split` is written on release, and a corrupt value gives the default.
|
||||
- The tab bar appears below 60rem.
|
||||
- The editor draft and the chat draft both survive a document change and a tab switch.
|
||||
- The embedded chat never changes `location.hash`.
|
||||
|
||||
The webui suite and every `scripts/test-*.sh` pass.
|
||||
|
||||
### Out of scope
|
||||
|
||||
Pins (P3), context (P4), and the full `#/chat` route.
|
||||
|
||||
### Gate
|
||||
|
||||
Fenton approves the manifest, and the webui suite is green on Sage's gate.
|
||||
|
||||
## P2 Q195 bus pins and message context: thread_pins, message_context, four verbs
|
||||
|
||||
### Problem
|
||||
|
||||
Nothing stores a pin (M8). `thread.send` accepts no context (`broker.mjs:3480`), and `messages` has no
|
||||
column for it (`bus/schema.sql:70-82`).
|
||||
|
||||
### Owner and reviewer
|
||||
|
||||
- Owner: a Codex gpt-6.1-sol seat (higher-level coding: schema, broker, channel).
|
||||
- Reviewer: an Opus 5.5 seat that is not the author (Fenton).
|
||||
- After: Jason's yes on J1.
|
||||
|
||||
### Files owned
|
||||
|
||||
- A new `packages/bus/src/migrations/<next>.sql`, and `packages/bus/schema.sql`.
|
||||
- `packages/bus/src/broker.mjs`: `thread.pin`, `thread.unpin`, `thread.pins`, `context` on
|
||||
`thread.send`, and the `context` field in `message.read` and `inbox`.
|
||||
- `packages/plugins/channel/webui/index.mjs` (the allowlist).
|
||||
- `packages/webui/src/chat.mjs` and `chat-http.mjs` (the routes and the `context` key).
|
||||
- Tests in `packages/bus/tests`, `packages/plugins/tests` and `packages/webui/tests`.
|
||||
|
||||
### What ships
|
||||
|
||||
Design sections S5, S6 and C5. Tests:
|
||||
- A pin to another user's thread refuses `thread-not-owned`.
|
||||
- A non-document path refuses.
|
||||
- Re-pinning replaces the old pin.
|
||||
- `thread.pins` marks a path missing at HEAD.
|
||||
- Each bad `context` field refuses `invalid-context`, and the `messages` count is unchanged.
|
||||
- An agent's `message.read` returns `context` apart from `body`.
|
||||
- The schema step-count and restore-guard lists are updated (schema.test, T46).
|
||||
|
||||
### Out of scope
|
||||
|
||||
Any UI (P1, P3) and the editor (P4).
|
||||
|
||||
### Gate
|
||||
|
||||
Fenton approves the manifest. The bus, plugins and webui suites are green on Sage's gate.
|
||||
|
||||
## P3 Q195 pin and follow-you in the chat pane
|
||||
|
||||
### Problem
|
||||
|
||||
After P1 and P2 the pane can show a thread, but nothing pins it, follows the user, or handles a stale
|
||||
pin.
|
||||
|
||||
### Owner and reviewer
|
||||
|
||||
- Owner: a Haiku 5.5 subagent at high effort.
|
||||
- Reviewer: an Opus 5.5 seat that is not the author (Fenton).
|
||||
- After: P1 and P2.
|
||||
|
||||
### Files owned
|
||||
|
||||
`packages/webui/src/public/pane.js`, `chat.js` (the pin controls only), `pane.css`, and tests in
|
||||
`packages/webui/tests`.
|
||||
|
||||
### What ships
|
||||
|
||||
Design sections S2 to S5:
|
||||
- The pin and unpin controls, and the header state line.
|
||||
- The `mosaic-console-chat` key, cleared on sign-out.
|
||||
- The swap between pinned and follow-you threads.
|
||||
- The "Pin this thread here instead?" prompt.
|
||||
- The pins list with stale and unread marks.
|
||||
|
||||
Tests, using fixture documents only:
|
||||
- Two documents pinned to two threads swap correctly.
|
||||
- Leaving a pinned document brings back the follow-you thread.
|
||||
- A stale pin shows its actions and isn't removed on its own.
|
||||
- A pin with a thread not found shows "Unpin" only.
|
||||
|
||||
### Out of scope
|
||||
|
||||
Context (P4).
|
||||
|
||||
### Gate
|
||||
|
||||
Fenton approves the manifest, and the webui suite is green on Sage's gate.
|
||||
|
||||
## P4 Q195 send-time document context: editor selection() and the context chip
|
||||
|
||||
### Problem
|
||||
|
||||
The agent can't tell which document or passage the user means (`chat.js:232` sends `{body}` only), and
|
||||
the editor has no selection API (`editor/entry.mjs:8`).
|
||||
|
||||
### Owner and reviewer
|
||||
|
||||
- Owner: a Codex gpt-6.1-sol seat. Mapping ProseMirror positions to Markdown source lines is the hard
|
||||
part.
|
||||
- Reviewer: an Opus 5.5 seat that is not the author (Fenton).
|
||||
- After: P2, and row 214 (C2-11a) on the editor, so the two don't edit the editor package at the same
|
||||
time.
|
||||
|
||||
### Files owned
|
||||
|
||||
`packages/webui/editor/view.mjs` and `entry.mjs` (`selection()` only), the rebuilt editor bundle as the
|
||||
row 154 build makes it, `packages/webui/src/public/pane.js` and `chat.js` (the chip and the `context`
|
||||
on send), and tests in `packages/webui/tests`.
|
||||
|
||||
### What ships
|
||||
|
||||
Design sections C1 to C6. Tests:
|
||||
- `selection()` gives the right lines for a fixture with headings, lists and a wikilink.
|
||||
- It gives null on a dirty draft.
|
||||
- The chip appears and can be removed.
|
||||
- No context is sent with no document open.
|
||||
- A dirty draft sends the path and commit without `lines`.
|
||||
- No send carries document text.
|
||||
|
||||
### Out of scope
|
||||
|
||||
Agent edits of the document and replacement proposals (Gyro's section, gated under Q176).
|
||||
|
||||
### Gate
|
||||
|
||||
Fenton approves the manifest. The webui suite and the editor round-trip tests are green on Sage's gate.
|
||||
|
||||
## P5 Q195 wordcraft in the left pane: iframe host, its CSP route, and the postMessage bridge
|
||||
|
||||
### Problem
|
||||
|
||||
O2 puts a canvas app in the pane.
|
||||
- The app listens on `document` and `window` (`apps/wordcraft-web/src/web.rs:170`, `:377`).
|
||||
- It exports nothing to JavaScript (W2).
|
||||
- It can hold focus and F6 (E3).
|
||||
|
||||
Without a host and a bridge, the shell can't:
|
||||
- ask whether there is unsaved work,
|
||||
- read the selection,
|
||||
- move keyboard focus out of the editor.
|
||||
|
||||
### Owner and reviewer
|
||||
|
||||
- Owner: a Codex gpt-6.1-sol seat (WebAssembly bridge, CSP route and cross-document messaging).
|
||||
- Reviewer: an Opus 5.5 seat that is not the author (Fenton).
|
||||
- After: P1, Jason's rulings J6 and J7, and Gyro's row that brings wordcraft's source into the repo.
|
||||
|
||||
### Files owned
|
||||
|
||||
- `packages/webui/src/serve.mjs`: the iframe route and its CSP only.
|
||||
- A new `packages/webui/src/public/editor-wordcraft.js` (the host adapter).
|
||||
- The wordcraft web entry in the repo's copy (bridge messages only), at the path Gyro's row sets.
|
||||
- Tests in `packages/webui/tests`.
|
||||
|
||||
### What ships
|
||||
|
||||
Design sections E2 to E7 for O2.
|
||||
|
||||
Tests:
|
||||
- The Console page's CSP is byte-identical before and after. Only the iframe route has
|
||||
`wasm-unsafe-eval`.
|
||||
- Messages with the wrong origin or source are ignored.
|
||||
- F6 inside the iframe moves focus to the divider.
|
||||
- A `dirty` timeout counts as dirty.
|
||||
- A paste in the chat pane doesn't reach wordcraft.
|
||||
- The pane is resized once per animation frame during a drag.
|
||||
- A Markdown fixture's selection maps to lines, and a .docx fixture sends no `lines`.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- Building wordcraft and its ribbon (Gyro's rows).
|
||||
- Importing .doc and .qxd files (Q196).
|
||||
- A new cite locator (J7).
|
||||
|
||||
### Gate
|
||||
|
||||
Fenton approves the manifest. The webui suite is green on Sage's gate, and the CSP test is in that run.
|
||||
Reference in New Issue
Block a user