# Row 148 (#1642): a Console editor for the project's spec and docs Owner: Researcher. Reviewers: Filbert and Dewey. Gate owner: Sage. Round 1 built 2026-10-16 in worktree `/mnt/storage/scratch/researcher/q148` at base `fcea7d015`. Round 2 is in `/mnt/storage/scratch/researcher/q148r2`, detached at base `63c82f1c4`. **Round 3 (current) is in `/mnt/storage/scratch/researcher/q148r3b`, detached at base `18aee8fa1`**; the manifest, patches and shas in this directory are for that tree. Round 3 sections: "Round 3: line separators ...", "second failure", "stack verdict", "what happens today when `render` throws", "Probe and criteria, round 3", "Mutants, round 3", "Round 3 run", "Gate, round 3", "Decision 123, round 3". Ruling sage-0507, reaffirmed by sage-0522: no Toast UI, nothing vendored. The editor is a textarea over the Markdown source plus a preview built with `createElement` and `textContent` only. The decision against a bundled editor is in `LIBRARY.md`. ## What it does - Docs page `#/docs` lists, per project, `spec/north-star.md` and the Markdown files directly under `docs/`. `#/docs//` opens one with its last commit. - Save writes exactly what the human typed as one local commit `Edit `, authored by `business.human`, through a new human-only bus verb `project.file.save`. It carries the commit the editor opened as `base`. If the file's last commit differs, the save refuses `stale-base` and writes nothing. - The preview renders headings, paragraphs, lists, block quotes, code blocks, inline code, emphasis and links. Everything else, raw HTML included, shows as source text. A link needs an `http:`, `https:` or relative target, carries `rel="noopener noreferrer"`, and shows its target beside the link text. An image is alt text plus URL and never loads. - CSP unchanged. No `vendor.sha256`, no vendor route. ## Files (27; list in `changed-paths.txt`, hashes in `candidate-manifest.sha256`) Modified (21): `packages/project/src/{index,paths,read}.mjs`, `packages/project/README.md`; `packages/bus/src/{broker,client,runtime,server}.mjs`, `packages/bus/tests/spec-fixtures.mjs`, `packages/bus/README.md`; `packages/webui/src/{bus,cli,serve}.mjs`, `packages/webui/src/public/{bus.css,bus.js,index.html}`, `packages/webui/README.md`, `packages/webui/tests/{browser,shell,views}.test.mjs`; `docs/TOOLS.md`. New (6): `packages/project/tests/human-file.test.mjs` (7 tests), `packages/bus/tests/project-files.test.mjs` (8), `packages/webui/tests/project-file.test.mjs` (10), `packages/webui/tests/markdown-preview.test.mjs` (15), `packages/webui/tests/docs-browser.test.mjs` (1 browser test with the whole editor flow), `packages/webui/src/public/markdown-preview.js`. Round 1: 1421 insertions, 46 deletions. Round 2 on base 63c82f1c4: 27 files, 1605 insertions, 46 deletions (`row148.patch`, the full diff, new files included). Plumbing outside the brief's owned list, one line each (Filbert note 1, Dewey N4): - `packages/bus/src/client.mjs`: the client refused any request over 64 KiB before sending; a save carries up to 1 MiB of text, so the save verb alone gets the larger limit. - `packages/bus/src/runtime.mjs`: the broker is handed the project module's file functions (`listFiles`, `readFile`, `writeFile`) the same way it already gets `readSpec` and `writeRecord`. - `packages/bus/src/server.mjs`: the socket cut every request at 64 KiB; only a request that names `project.file.save` near its start may pass that, up to the save limit, and the parsed verb is checked again. The three file verbs also join the list that awaits project Git effects. - `packages/bus/tests/spec-fixtures.mjs`: the fixture broker needs the same three functions as the runtime. - `packages/webui/src/bus.mjs`: the new bus refusal codes (`stale-base`, `project-busy`, ...) need an HTTP status. - `packages/webui/src/cli.mjs`: the CLI builds the read callbacks and, under `--send` only, the save callback that `startServer` takes. ## Decision 123: the `public/` diff `public-diff.patch` is the whole `packages/webui/src/public/` change on base 63c82f1c4: 4 files, +429 -6. `markdown-preview.js` (new, 322 lines, ASCII only, an IIFE exposing `window.MosaicMarkdown = Object.freeze({ render, linkTarget })`), `bus.js` (+97 -5 for the Docs view and the one new write), `bus.css` (+9), `index.html` (+1 -1, one added `