plans: Q195 pane and pin, editor-independent host contract for O1, O2 and O3 (Q196), context validation per gyro-0009 (#448)
This commit is contained in:
1 parent
ac34d97862
commit
7caf8008b5
1 file changed
+160
-7
@@ -95,6 +95,99 @@ 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
|
L6. A chat pane action never touches `docStates`. An unsaved draft stays as it is when the user switches
|
||||||
threads, pins or unpins.
|
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
|
## 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
|
S1. What a session is. In the pane, a session is a bus thread (M1). A pin names a thread UUID and nothing
|
||||||
@@ -122,8 +215,9 @@ S3. Rules:
|
|||||||
|
|
||||||
S4. Two documents pinned to different threads. Document A is pinned to thread T1 and document B to T2.
|
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
|
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. When a reply lands in T1 while T2 is on
|
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
|
||||||
screen, it shows as an unread count on the pins list, not as a pane switch.
|
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
|
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
|
(M7), so a move happens in git outside the Console. Pins don't follow git rename detection, because a
|
||||||
@@ -173,9 +267,10 @@ chip.
|
|||||||
C5. Storage and delivery. `thread.send` takes the optional `context`. The broker validates it before the
|
C5. Storage and delivery. `thread.send` takes the optional `context`. The broker validates it before the
|
||||||
insert:
|
insert:
|
||||||
- `project` must be an existing project of the business.
|
- `project` must be an existing project of the business.
|
||||||
- `path` must pass `documentPath`.
|
- `path` must be a path the editor may open for that project: `documentPath`, or the library's mapped
|
||||||
- `commit` must be 40-hex.
|
paths on a library business. `documentPath` itself is not widened (gyro-0009).
|
||||||
- `lines` must be two positive integers with `from ≤ to`.
|
- `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`.
|
- Any extra key is refused with `invalid-context`.
|
||||||
|
|
||||||
The context is stored in a new table, `message_context(message, project, path, commit, line_from,
|
The context is stored in a new table, `message_context(message, project, path, commit, line_from,
|
||||||
@@ -221,6 +316,12 @@ J5. This design does not depend on the row 5 brief (`docs/plans/2026-09-13_webui
|
|||||||
#1507), which is waiting on Jason. If Jason's row 5 ruling changes the chat surface, rows P1 and P3 are
|
#1507), which is waiting on Jason. If Jason's row 5 ruling changes the chat surface, rows P1 and P3 are
|
||||||
re-measured.
|
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
|
## 7. Cross-row findings
|
||||||
|
|
||||||
X1. C2-10b's "Files owned" names `webui/src/public/bus.js`, but thread messages render in
|
X1. C2-10b's "Files owned" names `webui/src/public/bus.js`, but thread messages render in
|
||||||
@@ -232,7 +333,8 @@ X2. C2-09a's brief cites `broker.mjs:2331-2334` and `:290-291`. At 6c75887b0 the
|
|||||||
|
|
||||||
## 8. Rows
|
## 8. Rows
|
||||||
|
|
||||||
The order is P2 → (P1 ∥ P4) → P3. Merge-chaining applies (Q177): each row builds on the previous row's
|
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.
|
candidate and gets its own review.
|
||||||
|
|
||||||
## P1 Q195 split pane: editor and chat side by side with a keyboard divider
|
## P1 Q195 split pane: editor and chat side by side with a keyboard divider
|
||||||
@@ -256,7 +358,7 @@ only), a new `pane.js` and `pane.css`, and tests in `packages/webui/tests`.
|
|||||||
|
|
||||||
### What ships
|
### What ships
|
||||||
|
|
||||||
Design sections L1 to L6. Tests:
|
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 divider's ARIA attributes and keys: arrows, Shift with arrows, Home, End and Enter.
|
||||||
- The clamps at 28rem and 18rem.
|
- The clamps at 28rem and 18rem.
|
||||||
- `mosaic-console-split` is written on release, and a corrupt value gives the default.
|
- `mosaic-console-split` is written on release, and a corrupt value gives the default.
|
||||||
@@ -394,3 +496,54 @@ Agent edits of the document and replacement proposals (Gyro's section, gated und
|
|||||||
### Gate
|
### Gate
|
||||||
|
|
||||||
Fenton approves the manifest. The webui suite and the editor round-trip tests are green on Sage's 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