66 KiB
Row 154 (#1648, Q168), phase 2 build note
Written 2026-10-18 by the Researcher seat. Brief: docs/plans/2026-10-16_q168-wysiwyg-editor.md, blob 4dd89bb60b04.
Mos's decision 123 go for phase 2 is mos-0716 (conditions 1-4, answered below).
The candidate is a patch against 0f715a054 in the detached worktree /mnt/storage/scratch/researcher/r154-wt
(rebased from 5e0437659 after row 124 landed on bus.js and serve.mjs; both patches applied clean).
Status, 2026-10-18 16:52Z: built and tested. Results are in "Results" below (webui suite 162/162 with
MOSAIC_REQUIRE_EDITOR_BUILD=1). The text below was written before the first slot run; where it says "checked outside the
suites", the suites have since checked it too. Sections "First slot run" and "Results" supersede anything older here.
Updated 2026-10-18 after sage-0751 and sage-0757 (still no suite run; Sage's row 149 gate holds the slot, then me):
the esbuild install and binary check, the MOSAIC_REQUIRE_EDITOR_BUILD switch and its tests, a separate paste-path test
for the pasted-image change, the lock contents, and the corrections to Mos's text are below. The candidate is held as
it is until Mos answers whether editor.js stays committed (Sage asked, sage-0751 call 2); a "no" moves the build into
stack-copy.sh and changes the candidate.
First slot run, 2026-10-18 (16:40Z) and what it changed
The first run of editor-browser.test.mjs failed 3 of 4. Stopped there as Sage directed (sage-0759). Causes, and fixes
approved in sage-0762:
- Load-path test expected
view.linksto be[]; that was wrong. markdown-it never makes a link token of ajavascript:or a non-imagedata:target; it leaves them as text. The paragraph is therefore not flaggedrefused(that flag is set only when markdown-it yields alink_openwhose hreflinkTargetrejects, e.g.//evil.example/x), so the goodhttps://example.com/ain the same paragraph is a real link. The page is safe either way. The test now expects that one link, and keeps the safety assertions: no anchor with ajavascript:ordata:href, thejavascript:anddata:text shows as text, nothing ran, nothing was fetched.hostile.mdgained a[evil](//evil.example/x)paragraph (must be a source block) and a[sp](https://a b.example/)paragraph (must stay text). - Paste/link-bar test reopened the same URL and hash. A hash-only navigation does not reload, so the pasted links
were still on the page ("2 links, expected 0"). It now loads
about:blankfirst and asserts the pasted links and text are gone before the link bar is used, so a stale page cannot pass again. - Edit test. I first reported that the save might send the old bytes. That was a misreading of the log. A diagnostic
showed the save sent the edited text (
First para. More \*\*not bold\*\*.), and the editor DOM held it throughout. The failing assert was the Source view after the save: it showed the old file. Cause, in the product: a switch between Visual and Source calledrender(false), anddocViewre-reads any clean draft from the project (a saved draft is clean). In the test the project still served the pre-save file. In real use it would serve the saved commit, but the read is still a bug of this row: a view toggle costs a project read and can replace the draft with a newer commit. Row 148'sdocs-browser.test.mjscaught the same thing (it expects oneproject-fileread; the Source click made a second). Fix, insrc/public/bus.js: an explicit argument, no stored flag (a first version kept a one-shot flag on the draft state; Sage, sage-0771, pointed out it could leak into the next navigation).render(navigated, opts)passesoptstoview(get, path, q, opts)and on todocView(get, project, file, { reuseDraft }). Only the mode-click handler passes{ reuseDraft: true }; hashchange, Refresh, Reload, the/api/configre-render and the 10 s refresh pass nothing. Tests,editor-browser.test.mjs: the fixture countsproject-filereads. Test 3: none across Visual to Source and back, and the Source textarea equals the saved bytes. New test: two Visual/Source round trips read nothing; Reload reads exactly once; a switch after Reload reads nothing; going to#/docsand back reads exactly once. Counts are taken from what opening cost, because a cold load of a docs URL reads twice (the page draws again when/api/configanswers;bus.jsnear the end). That predates this row and is left alone. browser.test.mjspins the page's script count (querySelectorAll("script").lengthwas 4).index.htmlnow loads/editor.js, so the page has 5. I missed it because the first runs covered only the editor files; the suite found it. The assertion now lists the scripts (/editor.js,/brand.js,/app.js,/markdown-preview.js,/bus.js), so another added script still fails it. This is a change to a test row 154 did not list; Dewey and Filbert should look at it.
Candidate files (2026-10-18 16:54Z)
In this packet: candidate.patch (git diff --binary against 0f715a054, new files included; sha256
7ebc125f6edf94dd7efe5bb28af8b30926cdc88d916648998f930bc40f990ca1; git apply --check passes on a fresh detached
checkout of 0f715a054), changed-paths.txt (195 paths: 9 modified, 186 new) and candidate-manifest.sha256 (sha256 of
each path as it sits in /mnt/storage/scratch/researcher/r154-wt). Nothing is committed or pushed. The tests were run on
exactly these bytes; no edit followed the 16:50:56Z suite run except this packet.
| Path | Change |
|---|---|
packages/webui/third_party/editor/ |
New. 19 packages of upstream TypeScript source, byte for byte, each with its license file and package.json; NOTICE; PROVENANCE.json. 171 files. |
packages/webui/editor/ |
New. model.mjs, view.mjs, entry.mjs, editor.css, build.mjs, toolchain.mjs (the esbuild version, binary sha256 and MOSAIC_REQUIRE_EDITOR_BUILD check; never imports esbuild), BUILD.json. |
packages/webui/src/public/editor.js, editor.css |
New. Built output (757137 and 4462 bytes). |
packages/webui/src/public/index.html |
One line: the stylesheet link and <script src="/editor.js" defer>, before the page scripts. |
packages/webui/src/public/bus.js |
Visual/Source mode, mountEditor, unmountEditor, the visual layout. Read, save, stale-base and read-only paths untouched. |
packages/webui/src/serve.mjs |
The two static entries /editor.js, /editor.css. Nothing else. |
packages/webui/package.json, package-lock.json |
esbuild 0.28.2 exact devDependency, build:editor script; new lock (27 packages). |
packages/webui/tests/ |
New: editor.test.mjs, editor-provenance.test.mjs, editor-browser.test.mjs, helpers/editor-bundle.mjs, helpers/every-element.mjs. Changed: docs-browser.test.mjs (see below), serve.test.mjs (two paths in a list). |
packages/webui/README.md, docs/TOOLS.md |
"Visual editor" section; one paragraph. |
Not touched: the save route, bus verbs, the human writer, the CSP, markdown-preview.js, project-file.test.mjs.
Mos's conditions
- A license file per package, plus NOTICE. Every imported package has its own license file next to its source
and
NOTICElists all of them;editor-provenance.test.mjschecks both. Count: Mos wrote 20; the build reads 19. The 20th wasprosemirror-schema-basic, which phase 1 listed but nothing imports (the schema ismodel.mjs's own). It is not in the tree.entitiesis BSD-2-Clause; the other 18 are MIT (SPDX from eachpackage.json). - Link mark through
window.MosaicMarkdown.linkTarget; image becomes text.model.mjshas no other target test. The image node is an inline atom drawn as[image: alt] src, never an<img>. The browser test covers<script>,onerror,javascript:anddata:on the load path (a hostile file, plus the every-element doc) and the paste path (HTML and plain text, then the link bar). Change made after phase 1's text: the image node'sparseDOMaccepted anydata-srcon paste; it now requireslinkTarget(data-src) !== null. See "Corrections to Mos's text" for why this is a change to the condition's wording, and its own test. - OSV re-run, positive control saved, added to osv.sh.
repro/osv.shnow runs the control first (dompurify 2.3.3 -> 20 advisories, markdown-it 12.2.0 -> 3) and exits 3 if OSV returns fewer. It also readsPROVENANCE.json, because the imported source is in no lockfile. Run 2026-10-18T16:28Z: control 20 and 3; the lock (27 packages) 0 advisories,results/osv-phase2-lock.json; the 19 imported versions 0 advisories,results/osv-phase2-imported.json. - esbuild binary sha.
@esbuild/linux-x640.28.2, sha256e1698a3d5c6c0798fee4fd3b5cc816651f460c63d390a7a26ea4beb0b1884100. Lock integrity: esbuildsha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==,@esbuild/linux-x64sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==(identical to the phase 1 scratch lock). Builds:results/build-hashes.txt(editor.jsc601f6c3…6565, editor.css8654ac57…ee61f, two absolute paths,BUILD.jsonidentical).
Corrections to Mos's text
- 19 packages, not 20. Mos's condition 1 counts 20.
prosemirror-schema-basicwas in phase 1's list but has no importer (the schema ismodel.mjs's own), so it is not imported and the build does not read it. 19 packages, 133 build inputs, 171 files inthird_party/editor/: 170 in the tree hash (the 19 packages plusNOTICE) andPROVENANCE.json, which records them. - The pasted-image rule changed. Mos's text said the image node becomes text, drawn never as an
<img>. That held for the load path. On the paste path the node accepted anydata-srcfrom aspan.md-image, the editor's own copy format, so a pastedjavascript:ordata:target became an image node and a save wrote. Nowmodel.mjsaccepts that span only whenwindow.MosaicMarkdown.linkTargetaccepts itsdata-src; a refused span is dropped to its text. Test:editor-browser.test.mjs, "a pasted image span is kept only for a target the link gate accepts, and a save writes only those". It pastes two accepted targets (https://ok.example/a.png,../img/r.png) and seven refused ones (data:,DATA:,javascript:,javascript:,vbscript:,//host, a target with a space) and checks the editor DOM (image nodes for the two, drawn as[image: alt] src, none for the rest), that nothing is fetched or injected, and the saved file (andin order, no refused target anywhere, the surrounding text kept). The older paste test still covers the span, HTML and plain-text cases together.
esbuild lock contents
packages/webui/package-lock.json, lockfileVersion 3, sha256 eb60a494…f63f
(eb60a4945282a5eae49a03a27cc4a1eb67954e73d57b8ef945073ed5c184f63f). Root entry: @mosaic/webui 0.1.0,
devDependencies esbuild 0.28.2. 27 packages, not 2: esbuild lists every @esbuild/* platform package as an
optional dependency, so the lock holds all 26 of them plus esbuild. npm ci installs only the one that matches the
host's os and cpu, so a linux-x64 install holds two (esbuild, @esbuild/linux-x64). All 27 are version 0.28.2,
dev, and (except esbuild itself) optional. Integrity values are the lock's, verbatim.
| Package | os / cpu | Integrity |
|---|---|---|
node_modules/esbuild |
sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA== |
|
node_modules/@esbuild/aix-ppc64 |
aix / ppc64 | sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ== |
node_modules/@esbuild/android-arm |
android / arm | sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg== |
node_modules/@esbuild/android-arm64 |
android / arm64 | sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A== |
node_modules/@esbuild/android-x64 |
android / x64 | sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q== |
node_modules/@esbuild/darwin-arm64 |
darwin / arm64 | sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw== |
node_modules/@esbuild/darwin-x64 |
darwin / x64 | sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw== |
node_modules/@esbuild/freebsd-arm64 |
freebsd / arm64 | sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw== |
node_modules/@esbuild/freebsd-x64 |
freebsd / x64 | sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg== |
node_modules/@esbuild/linux-arm |
linux / arm | sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w== |
node_modules/@esbuild/linux-arm64 |
linux / arm64 | sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug== |
node_modules/@esbuild/linux-ia32 |
linux / ia32 | sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ== |
node_modules/@esbuild/linux-loong64 |
linux / loong64 | sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ== |
node_modules/@esbuild/linux-mips64el |
linux / mips64el | sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA== |
node_modules/@esbuild/linux-ppc64 |
linux / ppc64 | sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ== |
node_modules/@esbuild/linux-riscv64 |
linux / riscv64 | sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA== |
node_modules/@esbuild/linux-s390x |
linux / s390x | sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg== |
node_modules/@esbuild/linux-x64 (the binary this build uses) |
linux / x64 | sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ== |
node_modules/@esbuild/netbsd-arm64 |
netbsd / arm64 | sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw== |
node_modules/@esbuild/netbsd-x64 |
netbsd / x64 | sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw== |
node_modules/@esbuild/openbsd-arm64 |
openbsd / arm64 | sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ== |
node_modules/@esbuild/openbsd-x64 |
openbsd / x64 | sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw== |
node_modules/@esbuild/openharmony-arm64 |
openharmony / arm64 | sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q== |
node_modules/@esbuild/sunos-x64 |
sunos / x64 | sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g== |
node_modules/@esbuild/win32-arm64 |
win32 / arm64 | sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ== |
node_modules/@esbuild/win32-ia32 |
win32 / ia32 | sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA== |
node_modules/@esbuild/win32-x64 |
win32 / x64 | sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g== |
Packet findings: F1 the imported punycode.js/package.json is the one in tag v2.3.1 (git blob b8b76fc7…);
that file says "name": "punycode", the npm tarball says "punycode.js". It is metadata only: the build aliases
punycode.js to third_party/editor/punycode.js/punycode.js by path and reads no name. F2 done. F3 done (condition 3). F4 done
(19 license files in licenses/, and in the tree).
Things a reviewer should know
- Dependency install for the gate (Sage's call 1, sage-0751).
packages/webui/package.jsonhas a devDependency,esbuild0.28.2exact, andpackages/webui/package-lock.jsonis new. The repo root has no workspaces and rootnode_modules/esbuildis 0.28.1 (it comes in through pi-coding-agent); the editor never uses it. The install isnpm ci --ignore-scripts --no-audit --no-fundrun insidepackages/webui, intopackages/webui/node_modules(gitignored). On linux-x64 it installs two packages,esbuildand@esbuild/linux-x64; the lock lists all 27 (see "esbuild lock contents"). - Binary check, refused on a mismatch.
editor/toolchain.mjsholds the sha256 of the 0.28.2 linux-x64 binary (e1698a3d…4100, condition 4) and hashesnode_modules/@esbuild/linux-x64/bin/esbuildbefore any build. It refuses when the hash differs, when no hash is recorded for the platform, when the platform package is missing, or whenESBUILD_BINARY_PATHis set (that variable swaps the binary esbuild runs).build.mjscalls it before it imports esbuild, so the check holds fornpm run build:editorand for the rebuild test.node editor/toolchain.mjsruns it alone: it prints the binary path and hash, exit 0;skip: <reason>, exit 0;missing:exit 3;refused:exit 4. The gate runs it after the install and before the rebuild test. Checked now, outside the suites, against my scratch install: exit 0, hashe1698a3d…4100. MOSAIC_REQUIRE_EDITOR_BUILD=1(Sage's call 3). Without it, a missing esbuild, or another version, skips the rebuild test and prints the reason; with it, that is a failure. A wrong binary fails either way.1requires; unset, empty and0do not; any other value throws, sotruecannot quietly turn the requirement off. The landing gate sets it after the install. Tests, all ineditor-provenance.test.mjs: (a) over four fake installs (no esbuild, 0.28.1, 0.28.2 with a wrong binary, 0.28.2 with no binary) the verdict is skip, skip, fail, fail without the switch and fail in all four with it, plus the switch's own values andESBUILD_BINARY_PATH; (b) a copy oftoolchain.mjsin a temp directory with no esbuild above it, run as a program: exit 0 andskip:on stdout without the switch and with0, exit 3 andmissing:on stderr with1, non-zero withtrue; (c) the rebuild test itself, which uses the same verdict. So "fails without esbuild" is tested without needing esbuild to be absent from the machine running it. A gate run that sets the switch on a machine with the install runs the rebuild; one without the install fails.- The built bundle is committed. Held for Mos. That is the tension with Q167 D3 (no vendored prebuilt bundles). What
is vendored here is source; the bundle is a build output whose inputs, options and hashes are recorded and
re-checkable, and a test proves the bytes. Sage's reading: D3 bans unreviewable bundles; this is our own build.
stack-copy.shrunsnpm ciat the root only, so a live copy cannot build the editor; building at deploy would put the install into stack-copy. Sage asked Mos (sage-0751 call 2). I changed nothing for it. docs-browser.test.mjshas one change. The page now opens in Visual, row 148's tests drive the textarea, so the test file has asource()helper that clicks Source when#doc-textis absent, called at the four places that open a file. Assertions are unchanged.project-file.test.mjsis untouched.- esbuild warning.
[ignored-bare-import]forimport "./mark"inprosemirror-transform/src/index.ts(the package issideEffects: false;mark.tsonly exports functions). Benign; the build does not fail on it. - The bundle is not scanned for string-to-markup sinks. The row 148 sink scan applies to
editor/*.mjs. The upstream code uses DOM APIs. The bundle is checked forevalandnew Functiononly. The browser test checks the live DOM instead (no script, frame, object, style, form,on*attribute or fetched URL after load and paste). - Open risk, checked at test time. Whether a ProseMirror paste of
<img src=…>HTML makes the browser fetch it (prosemirror-view parses pasted HTML in a detached document). The browser test assertsperformanceresource entries hold no tracker URL after the paste.view.mjssets notransformPastedHTML; an<img>is dropped because the schema has no rule for it. If the test fails, the fix is atransformPastedHTMLthat removesimgbefore parsing.
Normalisations and limits (stated, not hidden)
- Open and save with no edit writes the same bytes. Checked (bare
vm, not the suite) ongoals-review.md; the suites check it for every.mdunderdocs/andspec/and for the every-element doc. - CRLF saves as LF, as in row 148. An unedited file keeps Save off.
- A block you edit is rewritten in the editor's form:
-bullets, soft breaks as newlines, reference links inline,<and&escaped. Untouched blocks are written back as they were. - Kept as source blocks, not edited visually: tables, HTML blocks, front matter, indented code, setext headings, strikethrough, link reference definitions, non-blank gap lines, and any block with a link target the gate refuses.
- Empty paragraphs vanish when saved with an edit. Two adjacent lists may merge on reparse. Copy/paste of an image atom inside the editor is lost. A redraw or a Visual/Source switch starts a new undo history.
- The visual editor opens only when
roundTrip(text) === text; otherwise the file opens in Source with a note.
Row 129 overlap
Row 129 also edits index.html and serve.mjs. My hunks are one line and two lines. Whichever lands second rebases.
tests/browser.test.mjs line 22 (sage-0775). Row 129 (Rocko, in review) edits the same assertion: it adds /chat.js,
a fifth script on its side. This row's list is ['/editor.js', '/brand.js', '/app.js', '/markdown-preview.js', '/bus.js'].
Whichever row lands second rebases that line and pins all six scripts, in page order. Neither row pins a bare count.
Dewey and Filbert: expect a conflict on that line, and on index.html and serve.test.mjs's path list, not a semantic one.
Results (slot run 2026-10-18, 16:48:49Z to 16:51:57Z)
All with MOSAIC_REQUIRE_EDITOR_BUILD=1, from the package-local esbuild 0.28.2, worktree at 0f715a054 plus the candidate.
Counts are awk sums over the spec logs in /mnt/storage/scratch/researcher/r154-logs4/ and r154-logs5/, not typed by hand.
| Run | Start-end (UTC) | Tests | Pass | Fail | Skipped |
|---|---|---|---|---|---|
editor-browser.test.mjs |
16:48:49Z, in the file-by-file run | 5 | 5 | 0 | 0 |
editor.test.mjs |
same run | 5 | 5 | 0 | 0 |
editor-provenance.test.mjs |
same run | 6 | 6 | 0 | 0 |
docs-browser.test.mjs (row 148, assertion unchanged: reads.file is 1) |
same run | 1 | 1 | 0 | 0 |
project-file.test.mjs |
same run | 10 | 10 | 0 | 0 |
serve.test.mjs |
same run | 4 | 4 | 0 | 0 |
markdown-preview.test.mjs |
same run | 19 | 19 | 0 | 0 |
browser.test.mjs (script list pin) |
16:50:55Z-16:50:56Z | 1 | 1 | 0 | 0 |
webui suite, node --test tests/ |
16:50:56Z-16:51:52Z | 162 | 162 | 0 | 0 (0 cancelled) |
The suite in the 16:48:49Z run failed once (162 tests, 161 pass, 1 fail: browser.test.mjs:21, the script count; item 4
above). The pin was fixed and the suite rerun at 16:50:56Z as Sage directed (sage-0775); that is the 162/162 line.
Process sweep at 16:51:57Z: nothing of mine left (node, chrome, by /proc cwd link). The slot was held with the busy check
gated with &&. The mandatory rebuild test ("a missing or wrong esbuild skips without it and fails with it; ...") ran and
passed in the suite (suite.log line 32); it was not skipped.
Not run: the root test-*.sh scripts (the Docker gate), the rest of the repository's suites, and any live step.
Build record (Mos's sage-0751 conditions via mos-0765)
- esbuild:
0.28.2, exact, frompackages/webui/package-lock.json(sha256eb60a4945282a5eae49a03a27cc4a1eb67954e73d57b8ef945073ed5c184f63f, 27 packages: esbuild and 26@esbuild/*), installed withnpm ci --ignore-scripts --no-audit --no-fundinpackages/webui. Never the root install's esbuild. - Binary:
node_modules/@esbuild/linux-x64/bin/esbuild, sha256e1698a3d5c6c0798fee4fd3b5cc816651f460c63d390a7a26ea4beb0b1884100.editor/toolchain.mjschecks it before the byte compare; a wrong or unlisted binary is refused (exit 4), a missing one fails (exit 3) when the switch is on. Re-checked 16:52Z:esbuild --versionprints0.28.2and the binary sha matches. - Command: from
packages/webui,node editor/build.mjs(same asnpm run build:editor). It bundles the entryeditor/entry.mjswith the aliases ineditor/build.mjs, which point at the TypeScriptsrc/of the 19 trees underthird_party/editor/. Options: bundle, iife, es2022, browser, legal-comments inline, unminified. Input list: the 133 files ineditor/BUILD.json. - Outputs (sha256, bytes):
src/public/editor.jsc601f6c3f75b7cf9898a9a52abfdab3e50badbcb5f66e57e6df57641878e6565, 757137;src/public/editor.css8654ac57d1e7a561110138ecd7f701c34a074017ff8c4cd2768d8083908ee61f, 4462. Both re-hashed 16:52Z from the worktree and equal toeditor/BUILD.json. A rebuild from the tree is byte-identical (esbuild prints a benign[ignored-bare-import]warning). - Tree: 171 files under
third_party/editor/(170 in the tree hash, plusPROVENANCE.json).
Tests that pin index.html or public/ structure (sage-0775)
Grepped packages/webui/tests/*.mjs and tests/helpers/*.mjs for index.html, <script, querySelectorAll("script,
stylesheet, querySelectorAll("link, public/, readdirSync, .js'/.css', and for #sections a, nav and count
patterns. What I found:
browser.test.mjs:22pins the script list. Changed (see above).serve.test.mjs:36lists the static paths served. Changed earlier:/editor.jsand/editor.cssadded.shell.test.mjs:40joinsapp.js,bus.jsandindex.htmland searches for strings; it counts nothing, and no string it looks for is in the added link or script tag.shell.test.mjs:22-33comparestokens.css,icons.svgand the font files; none changed.shell.test.mjs:266expects 9#sections svg use,:274-276the strip layout: the nav is unchanged, and the suite passes.bus.test.mjs:215fetches/bus.jsand/bus.cssfor status 200. Unaffected.markdown-preview.test.mjs:10readsmarkdown-preview.jsby path. Unaffected.helpers/editor-bundle.mjs(new) readsmarkdown-preview.jsandeditor.jsinto avm.pinned-copy.test.mjsandservice.test.mjscallreaddirSync, but on temp copies and unit directories, not onsrc/public/.#sections aselectors inbus-browser,send-browser,messages,docs-browserandshelltests select nav links, not page<link>or<script>tags.- Other
<scripthits (bus-browser,conversation,send-browser,docs-browser,markdown-preview,browser-edge) are hostile test strings, not structure.
Nothing else pins the number or order of scripts, links or stylesheets. The whole suite passing (162/162) is the check; the grep is the list of where I looked, and it is a grep, so a pin written some other way would show only in the suite.
Cold-load double read (not fixed; for a P19 follow-up)
packages/webui/src/public/bus.js:1062-1067: on load, fetch('/api/config') resolves and its .then calls render(false)
for /inbox, /settings and any /docs/ path (line 1067). The initial render(false) at line 1072 has already started,
so opening a docs URL cold reads the file twice (project-file is called twice). I saw it as the test "opening reads once"
getting 2. It predates this row. docs-browser.test.mjs (row 148) opens files by setting location.hash on a loaded page, not cold, though it does b.navigate to a docs URL for the ?noedit and ?throwrender pages (lines 240 and 248 on r1), which are cold loads. I left it alone and the new
read-count test counts from what opening cost (opened), so it passes whether or not that is later fixed. A fix would be
to skip the config re-render for /docs/ when the first render has not yet drawn a doc, which is a change to row 148's
flow, so it is not mine to make here.
Rebase onto 850484a7d (row 129 landed first), prepared 2026-10-18, nothing run
Sage (sage-0795): row 129 landed as 850484a7d, so this row is the second lander. The r1 candidate (base 0f715a054,
manifest ecaec329…) is untouched; Filbert is reviewing it. The rebase is a separate tree and a separate directory:
rebase-850484a7d/ in this packet (candidate.patch sha256 2b8ccdfbbf0b991cc3b05eb1ecc1958fa41fec96c44a7eb5bce092cb1187f6b1,
changed-paths.txt, candidate-manifest.sha256, 195 paths, same set as r1), worktree /mnt/storage/scratch/researcher/r154-wt2
(detached at 850484a7d, the patch applied with git apply --3way, staged). No test, build or server has run on it.
It is a preview for review of the conflicts, not a candidate. The tests, the rebuild check and a new manifest follow
Filbert's r1 verdict, the slot, and r2.
Conflicts and how each was resolved
Three files conflicted. Everything else applied or merged without a conflict.
packages/webui/src/public/index.html(head, lines 4-6). Row 129 added/chat.cssbefore/shell.cssand/chat.jsbefore/bus.js; this row adds one line holding/editor.cssand/editor.js. Resolved by keeping row 129's two lines as they are and inserting my line between them, as in r1. Page script order is noweditor, brand, app, markdown-preview, chat, bus.packages/webui/src/serve.mjs(static map, lines 25-33). Row 129 addedchat.jsto the script list andchat.cssto the stylesheet list; this row adds two explicit entries. Resolved by keeping row 129's lists and adding my/editor.jsand/editor.cssentries after them. No entry of either row changed.packages/webui/tests/browser.test.mjs(line 21). Row 129 changed the count from 4 to 5 and added ascript[src="/chat.js"]count of 1; r1 replaced the count with a list. Resolved with the list of all six, in page order:['/editor.js', '/brand.js', '/app.js', '/markdown-preview.js', '/chat.js', '/bus.js'], and kept row 129's/chat.jscount line unchanged under it. The count of 5 is gone (a pinned list says more).
Merges that applied without a conflict, and what I looked at
packages/webui/src/public/bus.js. Row 129 changed it (the Chat section,forget(), the/chatbranch inrender, the palette views, the config re-render condition). Mine are indocView,viewandrender's signature and the mode-click handler. The merge is textual with no overlap. Checked by reading, not by running:render(navigated, opts = {})still takesopts(line 866), the chat branch returns beforeview(...)sooptsis never needed there,view(get, path, q, opts)is at 821, anddocView(..., { reuseDraft })at 683. The behaviour of the combined file is untested until r2.packages/webui/tests/serve.test.mjs. Row 129 did not touch it on this path list; my one-line change (two editor paths) applied as in r1.docs/TOOLS.md,packages/webui/README.md. Row 129 appended its own sections at the end of both; mine are separate paragraphs and sections. No overlap.packages/webui/package.json,tests/docs-browser.test.mjs. Unchanged by row 129; applied as in r1.
The grep for structure pins, repeated on the new base
Grepped tests/chat*.mjs, views.test.mjs, shell.test.mjs, serve.test.mjs, browser*.mjs, bus*.mjs for
index.html, script and link selectors, stylesheet, /editor, /chat.js, /chat.css and src/public. Row 129 changed
shell.test.mjs (the #sections svg use count is 10 now, with a Chat link check): this row adds no nav item, so it does
not affect it. Nothing new pins the number or order of scripts or links beyond browser.test.mjs:22-23. Again a grep: the suite is the check.
Files that differ from the r1 candidate
By sha256 against r1's manifest, six files differ: docs/TOOLS.md, packages/webui/README.md, packages/webui/src/public/bus.js,
packages/webui/src/public/index.html, packages/webui/src/serve.mjs, packages/webui/tests/browser.test.mjs. The other 189 are
byte-identical to r1, including editor.js and editor.css, so the rebuild check does not change: editor.js is
c601f6c3…, editor.css is 8654ac57… (to be re-verified by running the rebuild test on the new base).
Still needed
Filbert's r1 verdict and any changes; the slot; the touched tests singly and the webui suite on the rebased tree, with
MOSAIC_REQUIRE_EDITOR_BUILD=1; a new manifest; Mos's fresh decision 123 go on the rebased public/ diff; r2.
r2 changes from Filbert's r1 verdict (CHANGES, #1648 comment 28954, rev 1794), written 2026-10-18T17:22Z (clock), nothing run
Made on the rebased tree only (/mnt/storage/scratch/researcher/r154-wt2, staged). The r1 candidate is untouched. No test has
run on any of this. The rebase packet in rebase-850484a7d/ (patch 2b8ccdfb…, manifest 971da9a4…) predates these edits and is
superseded when the slot run is done and the r2 manifest is cut.
- Blocking, the round-trip gate had no test (
bus.jsgatewindow.MosaicEditor.roundTrip(s.text) === s.text, Filbert's m3 forced it true and survived). New test intests/editor-browser.test.mjs, "a file the editor cannot write back byte for byte opens as Source…": it swapswindow.MosaicEditorfor{ mount: real.mount, roundTrip: t => t + ' ' }, reloads the file, and asserts#doc-textholds the file, the note "The visual editor is not available for this file, so it is open as source." shows, there is no.ProseMirror,#doc-saveis disabled, and Source is the pressed button. Same test, second half:window.MosaicEditor = undefined(the!window.MosaicEditorbranch of the gate) gives the same five facts. Expected killer for m3 (gate forced true): the firstdeepEqual(pm: falsefails because the editor mounts). Not yet run; the mutant run belongs to the slot and its result goes here. - Mode follows the screen. r1 compared a click with
docMode(the view asked for). On a fallbackdocModestays'visual'while Source is on screen, so Visual was ignored: a user who made the text round-trip in Source (it re-renders only on a switch) could not get back to the editor without clicking Source first. NowdocShownis set indocViewto what was drawn ('visual'only whenvisualOk), and the click handler compares with it;docModestays the preference. I did not setdocMode = 'source'on fallback, because docMode is shared across files and one unwritable file would then open every later file as Source. The same test covers it: after a fallback, with the stub'sroundTripfixed, one click on Visual opens the editor with no new read; the next fallback is a fallback again (preference kept); with no editor, a click on Visual leaves Source and the note. Expected killer for reverting tomode === docMode: the Visual-clickh.pm()wait times out. - docs-browser wait restored. r1's
source()helper dropped#doc-committextContent !== ""that row 148's?throwrenderwait had (the assertiontext('#doc-commit') === C1.slice(0, 12)follows it). It is back in both waits insource()(tests/docs-browser.test.mjs), so every use of the helper has it, not only the one line.
Files changed since the rebase packet: packages/webui/src/public/bus.js, tests/editor-browser.test.mjs, tests/docs-browser.test.mjs.
Path set is still the same 195. editor.js and editor.css are not touched, so the rebuild shas stay as in the build record.
Design choice for review (Sage accepted docShown in sage-0799, sent about 17:22:30Z; its own stamp said 17:24Z, corrected by sage-0800): a fallback does not set docMode = 'source'. docMode is one variable
shared across every file in the page, so one file the editor cannot write back would force every later file into Source, including files that
round-trip. docShown is the view drawn by the last docView and is what a click compares with; docMode stays the user's preference.
The "expected killer" wording above is a prediction. The killers are named from the slot run below, once it has happened.
Dewey's r1 finding: line breaks in link titles, image alt/title and fence info (sage-0802; written, first slot run 2026-10-18T17:57Z)
Dewey's verdict comment is the source of record; this records what editor/model.mjs does and where I went past sage-0802.
The four sinks: a link mark's title, an image's alt and title, and a code block's params. Each is written into one line of Markdown, a paste can set any of them, and a line break in one let a following <img …> start an HTML block in the saved file.
- Link title, image alt, image title:
oneLinereplaces each run of CR/LF with one space on the way out. - Code block info:
fenceInfokeeps the first line only, and drops the info if it holds a backtick (a backtick info is not legal on a backtick fence). - Load side: a
fencetoken whose info holds a backtick (legal only on a tilde fence) setsrefused, so that one block loads as a raw source block (pre.md-rawinside the visual editor, fromnodeOf, model.mjs:265); the rest of the file stays visual. Without that, the first edit would have written the block back without its info.
Deviations from sage-0802: (i) the info is cut at the first line break, not the first whitespace, so js {1,3} keeps its attributes; (ii) the tilde-fence backtick case loads as a raw source block (that block only, not the whole file as Source), as above; (iii) a single newline in a title or alt that came from a file is collapsed only when its block is edited (an untouched block is written back from the source bytes).
Can the editor's own reading of a file produce these values? markdown-it cannot give a blank line in a title, alt or info string (a blank line ends the paragraph). It can give one newline in a title or alt, spaces in an info string, and a backtick in a tilde-fence info. So the T\n\n<img …>\n\n payload is reachable by paste only.
Not fixed, noted: a title ending in a backslash is written without escaping the backslash ("a\\"); it needs a separate look and is outside the finding.
Tests added in tests/editor-browser.test.mjs: one per sink on the paste path (assert the written string, no line starts <img, no blank line then a tag, h.quiet()), plus the tilde-fence test.
r2 slot run 1, 2026-10-18T17:57Z (clock) — stopped at the first failure
Slot from sage-0803. Busy check CLEAR 17:56:43Z. npm ci --ignore-scripts exit 0 (lock sha eb60a494… and esbuild binary sha e1698a3d… both matched). Build twice, exit 0, byte-identical. New src/public/editor.js sha256 32fcaa18eaee2ff426c3c7e06038ee190f9fd7a332c9f3db349aa74df22c9fb5, 758277 B (r1: c601f6c3…, 757137 B); editor.css unchanged, 8654ac57…, 4462 B.
| test file | exit | tests | pass | fail |
|---|---|---|---|---|
| editor | 0 | 5 | 5 | 0 |
| editor-provenance | 0 | 6 | 6 | 0 |
| editor-browser | 1 | 11 | 10 | 1 |
(sums by awk over r154-r2-logs1/*.log: 22 / 21 / 1). Stopped there; docs-browser, browser, project-file, serve, markdown-preview, shell, the suite and the mutants did not run. Failure: "a code block info string pasted with line breaks…" — the saved file was the original text, no code block and no <img; the written assertion at line 445 failed. Cause not yet known. Slot released 17:57:30Z.
Test fix after run 1 (sage-0806), written 2026-10-18T18:01Z (clock), not run
Why the fourth paste test saw no code block: not isolated. What the code says: parseFromClipboard (third_party/editor/prosemirror-view/src/clipboard.ts:98) runs Slice.maxOpen over HTML that is not ProseMirror's own, so the first and last node of a pasted slice are open and join the text at the cursor. A lone <pre> at the end of a paragraph is such a case; the code_block parseDOM rule (model.mjs:44) does read data-params, so params are settable by paste when the block stays whole. What does not fit that reading: the first run's Save sent the original text with no code in it, where a join would have added the text to the paragraph. I did not run an experiment to settle it.
Changes to all four paste tests (editor-browser.test.mjs): each now waits for the pasted node to appear in the editor's DOM (a[title], span.md-image[data-alt], span.md-image[data-title], pre[data-params]), asserts its attribute equals the hostile value (line breaks and tag included), and only then waits for Save, saves and checks the written text. The <pre> is pasted between two paragraphs (<p>x</p><pre …>code</pre><p>y</p>), so it is a whole middle node and does not depend on the unexplained case. The expected fence is T\ncode\n (info cut at the first line break).
Deviations from sage-0802: Dewey accepts or refuses each one separately
- Fence info is cut at the first line break, not at the first whitespace. Reason: an info string of one line cannot open an HTML block, and cutting at whitespace would drop attributes such as
js {1,3}that a file may rely on. Dewey: accept / refuse. - A tilde fence whose info holds a backtick loads as a raw source block (
refused, model.mjs:150;nodeOfmakes that block a raw_block, shown aspre.md-raw, editor-browser.test.mjs:499-500). Only that block is source; the file stays in the visual editor. Reason: the serialiser must drop a backtick info on a backtick fence, so editing that block would silently lose the info; as a raw block the bytes are kept. Dewey: accept / refuse. - A single newline in a loaded title or alt is collapsed only when its block is edited. Reason: an unedited block is written from its source bytes, so a file opened and saved with no edit round-trips byte for byte. Dewey: accept / refuse.
For the reviewers: the first run's unchanged file is still unexplained. The new paste tests do not depend on an explanation; each fails at the node wait if its paste is dropped, so a dropped paste cannot pass as a clean save. If the second run shows the
is dropped even between paragraphs, the params half of Dewey's finding is not reachable by paste in this editor and Dewey decides whether it stands.r2 slot run 2, 2026-10-18T18:11Z (clock): green, and the run-1 failure explained (this section corrects lines 408, 412 and 422 above)
Correction. I wrote that run 1's saved file was the original text and that the cause was unknown. That was a misreading: the saved text began
# codeTitle, the fixture's heading being# Title. The paste did land, at the start of the document (selectAllChildrenthencollapseToEndleaves the cursor in the first block, the heading, not the last), andSlice.maxOpenopened the lone<pre>so its text "code" joined the heading and no params were set. The mutant logs show the same landing point (# x …,# [word](…)Title). The "end of a paragraph" wording in lines 412 and in the test comment was wrong; the test comment is corrected in the final bytes (comment only, after the run). The new tests are unaffected: a<pre>between two paragraphs is a whole node wherever the cursor is.Slot from sage-0812; busy check CLEAR 18:08:33Z; run 18:08:33Z to 18:10:36Z; cwd sweep clear 18:10:48Z. Lock sha eb60a494… and esbuild binary sha e1698a3d… checked; build twice, byte-identical.
src/public/editor.jssha256 32fcaa18eaee2ff426c3c7e06038ee190f9fd7a332c9f3db349aa74df22c9fb5, 758277 B;editor.css8654ac57d1e7a561110138ecd7f701c34a074017ff8c4cd2768d8083908ee61f, 4462 B.Singles (awk-summed over
r154-r2-logs2/*.log): 62 tests, 62 pass, 0 fail, 0 skipped — editor 5, editor-provenance 6, editor-browser 11, docs-browser 1, browser 1, project-file 10, serve 4, markdown-preview 19, shell 5. Webui suite withMOSAIC_REQUIRE_EDITOR_BUILD=1: 201 tests, 201 pass, 0 fail.Mutants, killers from the logs (editor-browser, 11 tests each; files restored and cmp'd byte-identical afterwards):
| mutant | result | killed by |
|---|---|---|
round-trip gate forced true (roundTrip(s.text) === s.text → true) |
1 fail, 10 pass | "a file the editor cannot write back byte for byte opens as Source…", assertion "a failed round trip shows the source, the note and a Save that is off" (test line 397) |
docShown revert (mode === docShown → mode === docMode) |
1 fail, 10 pass | the same test, "Condition timed out: #doc-visual .ProseMirror" (the Visual click did nothing) |
serialiser verbatim (oneLine identity, fenceInfo returns the value) |
4 fail, 7 pass | the four paste tests: link title, image alt, image title, code block info string, each "the value is on one line" |
So the params half of Dewey's finding is reachable by paste when the <pre> is whole, and the tests show the clamp working.
r2 candidate files (2026-10-18T18:12Z, clock)
r2/ in this packet: candidate.patch (git diff --cached --binary against 850484a7d, sha256 3c7c0114410d8194d10d3b3c1c7b7dbdee3db17099813f95f793451e8ada41eb; git apply --check passes on a clean export of 850484a7d), changed-paths.txt (195 paths, the same set as r1 and the rebase preview) and candidate-manifest.sha256 (sha256 1db8d410ca4d706a355ce3d1dec3e985ae058093a4a56f5a31521079a4cce017; each path as it sits in /mnt/storage/scratch/researcher/r154-wt2). Nothing is committed or pushed. The base moved from 0f715a054 (r1) to 850484a7d, so the patch is not r1's patch plus a delta; the r1 packet stays as it was.
Every byte was in the run-2 tree except one edit after it: the comment above the four paste tests in tests/editor-browser.test.mjs (now sha256 5b050de8…), from "…a lone
pasted at the end of a paragraph left the file unchanged in the first r2 run; the cause was not isolated, so this paste does not depend on it." to "…so a lonejoins the block the cursor is in as plain text and sets no params; the paste here lands in the first block, the heading." Comments only;node --checkpasses. The rebuilteditor.jsandeditor.cssare the run-2 files (shas above);editor/BUILD.jsonis the one the build wrote.r2 correction: editor/BUILD.json in the candidate is the mutant's (found after sage-0831, 2026-10-18T18:35:34Z)
Run 2 did set
MOSAIC_REQUIRE_EDITOR_BUILD=1(run2 script line 8, exported before anything ran), and editor-provenance passed 6/6 in it (r154-r2-logs2/editor-provenance.log). That result was true when it ran. The candidate was cut from a tree that was no longer what that run measured.Cause: the serialiser-verbatim mutant edits
editor/model.mjsand runsnode editor/build.mjs. The build rewriteseditor/BUILD.jsonwith the mutant's editor.js (73975eb2…, 758168 B;serialiser-verbatim.build.log). The mutant function restored model.mjs and editor.js but never BUILD.json, and the closing "files restored byte-identical" check covered only bus.js, model.mjs and editor.js. So BUILD.json stayed at the mutant's values, the patch and manifest (BUILD.json 317e8d78…) carried them, and the shipped editor.js (32fcaa18…, 758277 B) no longer matched its record.My restore check was incomplete and my "restored byte-identical" line overstated what it verified. For round 3: rebuild so BUILD.json records 32fcaa18…/758277, and the mutant script must back up and restore BUILD.json and include it in the closing check (or the check must cover every path in the manifest).
Round 3 changes, written 2026-10-18T18:54:19Z (nothing run yet; results go in a section below after the slot run)
Answers to the r2 verdicts (Dewey 28979, Filbert 28977):
- Backslash in a link or image title (Dewey, blocking).
editor/model.mjs: newtitleBackslashdoubles every backslash in a link title (afteroneLine) before the vendored link writer, which escapes only"; the linkcloseoverride now uses it. The image writer escapes[\\"]with'\\$&', so backslash and quote both get a backslash. A pasted titlex\") <img src=x onerror=alert(1)>is now written"x\\\") <img src=x onerror=alert(1)> ", which reads back as that same title; a title ending in\is written"end\\". Unedited blocks are still written from their source bytes, so the round trip of a file is unchanged.- Tests (editor-browser.test.mjs). The four line-break paste tests now share one helper (
pasteTest); same assertions as r2. Added, for a link title and an image title each: a title holding a quote after a backslash then a tag, and a title ending in a backslash (four tests). Each waits for the pasted node to hold the value in the document, saves, and asserts the exact written string, so each fails on r2 (r2 writes"x\\") <img…> "and"end\"). (Superseded after sage-0844, before the run: the four tests also re-read the saved text, see "Round 3 results".)- g5 (Filbert). New test:
<pre data-params="ab">pasted between two paragraphs and saved must write a bare fence (```\ncode\n```). Mutant g5-backtick-kept in the run script drops the backtick test infenceInfo`.- docs-browser.test.mjs:67 (Filbert). The second wait in
source()has the explicitdocument.querySelector("#doc-commit")existence check back, as line 65 has.- Tilde wording (Dewey ii, Filbert). The three lines above that said "opens as Source" now say that block loads as a raw source block (
pre.md-raw,nodeOfmodel.mjs:265) and the file stays in the visual editor. The behaviour is unchanged.- BUILD.json (both, blocking). The cause is in the r2 correction section above. Round 3 rebuilds it; the run script checks that BUILD.json records the shipped editor.js sha256 and byte count right after the rebuild.
- Dewey non-blocking:
<img>in an image alt stays in the alt for CommonMark renderers; I leave it as is and note it for him.Run script
/mnt/storage/scratch/researcher/r154-r3-run.sh, in the order of sage-0840: toolchain sha check, build twice (editor.js and BUILD.json byte-identical), every webui test file singly, the suite, a baseline manifest of all candidate paths, seven mutants (gate-true, docshown-revert, serialiser-verbatim, info-verbatim, g5-backtick-kept, link-backslash, image-backslash), each backing up and restoring the file, editor.js, editor.css and BUILD.json; thensha256sum -cover the whole manifest (stop on any failure), provenance under MOSAIC_REQUIRE_EDITOR_BUILD=1,sha256sum -cagain, and only then the patch and manifest, with a clean-export apply check. Every mutant string was counted once in its file before the run.Round 3 results (slot run 2026-10-18T18:55:31Z to 19:01:13Z; logs in
r3/)Run script
/mnt/storage/scratch/researcher/r154-r3-run.sh, exit 0, final linedone; outputr3/run.out.
- Toolchain.
package-lock.jsonand the esbuild binary matched their pinned sha256 values. Two builds, editor.js and BUILD.json byte-identical. editor.js9c881b13740fae2408717ad75fa1d85bfcfe3fa8b3af0f1b3f2e706c90e80fee(758420 B), editor.css8654ac57d1e7a561110138ecd7f701c34a074017ff8c4cd2768d8083908ee61f(4462 B).editor/BUILD.jsonrecords that editor.js sha256 and byte count (checked by the script right after the rebuild). - Singles. All 32 webui test files singly (
r3/_singles.txt): 206 tests, 206 pass, 0 fail, 0 skipped (awk sum over the 32 logs). - Suite.
node --test tests/: 206 tests, 206 pass, 0 fail, 0 skipped (r3/suite.log). Same total as the singles. - Baseline. 195 changed paths, the same set as r2 (
cmpagainst r2's list).sha256sum -cover all 195 passed before the mutants. - Mutants (each run on
tests/editor-browser.test.mjs, 16 tests; each backs up and restores the file, editor.js, editor.css and BUILD.json). All seven exited 1; 112 tests run, 101 pass, 11 fail (awk sum over the seven logs).
| mutant | fails | killing tests |
|---|---|---|
gate-true (roundTrip(s.text) === s.text replaced by true) |
1 | "a file the editor cannot write back byte for byte opens as Source, says so, cannot save…" (Save not off) |
docshown-revert (mode === docShown to mode === docMode) |
1 | the same test (timed out waiting for #doc-visual .ProseMirror) |
serialiser-verbatim (oneLine identity) |
3 | link title, image alt and image title "pasted with line breaks in it is written on one line…" |
| info-verbatim (fence info not cut at the first line) | 1 | "a code block info string pasted with line breaks in it is written on one line…" |
g5-backtick-kept (fenceInfo keeps a backtick) |
1 | "a code block info string with a backtick, pasted, is dropped on Save, so the fence stays closed" (the new g5 test) |
| link-backslash (link title no longer backslash-doubled) | 2 | "a link title holding a quote after a backslash then a tag is written with the backslash escaped…" and "a link title holding a trailing backslash is written…" |
image-backslash (image title escapes " only) |
2 | the same two tests for an image title |
What the re-read asserts do and do not show. The four backslash tests assert the exact written string first, then (with reread) mount a read-only editor on the saved text and assert the parsed title equals the pasted value and no <img is in the document text. In the link-backslash and image-backslash mutants the first assertion throws, so the test stops there: the logs show the exact-string AssertionError for all four failures and never reach the re-read. sage-0844 expected both asserts to fail on those mutants; I can show only the first. The re-read asserts passed on the shipped code in all runs (singles, suite), and I have not run a mutant that gets past the exact-string check to see the re-read fail alone. Say so if a reviewer wants that run (a title-escape mutant that writes a different but still wrong string would do it); until then, the exact-string assert is the killer of record.
- After the mutants.
sha256sum -cover the whole manifest against the tree: 195 OK of 195, no failure (r3/post-mutant-check.txt). editor-provenance underMOSAIC_REQUIRE_EDITOR_BUILD=1after the mutants: 6 tests, 6 pass (r3/provenance-post-mutant.log).sha256sum -cagain before the cut: 195 OK (r3/pre-cut-check.txt). - Cut last.
git add -A packages docs, path list equal to the baseline,git diff --cached --binarytor3/candidate.patch(sha256d709b2dd390b7c01e6ef96aa7eced91287d1a48b7e003d74db3645f57a8c3db1;git apply --checkpasses on a clean export of850484a7d).r3/candidate-manifest.sha256is identical to the checked baseline, andsha256sum -cover it passed 195 OK of 195 (r3/cut-manifest-check.txt). Manifest sha256 (the candidate digest):cebca5145c899a362e938d36b26168070ad8cc01bfdc0c9f44a68eee07a005ca. Nothing committed or pushed.r2/andr1/are unchanged.
Answers to the non-blocking notes:
- g5 (Filbert). New test and mutant g5-backtick-kept above; the mutant is killed by it.
- Tilde wording (Filbert, Dewey ii). Fixed at the three places; only that block is a raw source block.
- docs-browser.test.mjs:67 (Filbert). The existence check is back; the file passed singly and in the suite.
- Trailing backslash in a title (Filbert). Written escaped now; link and image tests, killed by their mutants.
<img>in an image alt (Dewey). Left as is: the alt stays in the alt for a CommonMark renderer; the editor's tokenizer hashtml: false, so it makes no inline HTML node of it.
Line refs corrected after Dewey 28985 (r2 numbers were stale in r3): nodeOf is model.mjs:265, the raw-block asserts are editor-browser.test.mjs:499-500. Text only; the candidate is untouched.
Rebase onto df4d5702f (row 125 landed first, sage-0884), prepared 2026-10-18, nothing run
Row 125 landed as aa625565f; df4d5702f is origin/refactor after Dewey's packet commit. The r3 candidate (base 850484a7d, manifest cebca514…05ca, approved by Dewey 28985 and Filbert 28986) is untouched in r3/. The rebase is a separate tree, /mnt/storage/scratch/researcher/r154-wt3 (detached at df4d5702f, packages/webui/node_modules copied from the r3 tree), and gets its own directory in this packet after the slot run. I applied r3/candidate.patch with git apply --3way --index: no conflicts, 0 unmerged paths, no conflict markers, the same 195 paths as r3 (cmp of the two path lists). No test, build or server has run on it.
What 125 changed in webui (git diff 850484a7d df4d5702f, nine files): chat-http.mjs, chat.mjs, public/bus.js, serve.mjs, tests/channel-chat.test.mjs, chat.test.mjs, limits-browser.test.mjs (new), limits-route.test.mjs (new), shell.test.mjs; and docs/TOOLS.md.
By sha256 against r3's manifest, three of my 195 files differ: docs/TOOLS.md, packages/webui/src/public/bus.js, packages/webui/src/serve.mjs. Everything else is byte-identical to r3, including editor.js, editor.css, editor/BUILD.json and every file under editor/ and third_party/editor/.
serve.mjs: 125 added thelimitsverb andresetto the settings keys; my hunk is the two static-map entries/editor.jsand/editor.css. Different regions; the combined diff against the new base is only my two lines.public/bus.js: 125 changed 28 lines (settings and limits); my hunks are indocView,view,render's signature,paintDoc, the mode-click handler and theunmountEditorcalls. Read the combined diff against the new base: it is exactly my r3 hunks,optsstill reachesdocViewthroughview. The behaviour of the combined file is untested until the slot run.docs/TOOLS.md: 125 added 11 lines elsewhere; my paragraph in the Console section applied as in r3.- All six scripts stay pinned.
index.htmlandtests/browser.test.mjsare not touched by 125, so the pin atbrowser.test.mjs:22is still['/editor.js', '/brand.js', '/app.js', '/markdown-preview.js', '/chat.js', '/bus.js'], in page order, andindex.htmlloads them in that order. Row 125 added no script. Neither row pins a bare count. - Structure-pin grep on the new tests (
limits-browser,limits-route,channel-chat,chat,shell): nothing selects scripts, stylesheets orindex.html;shell.test.mjsreadsapp.js,bus.js,index.htmlas text for other checks. The suite is the check.
Run plan (/mnt/storage/scratch/researcher/r154-rb-run.sh, derived from the r3 script; the diff against it is the base, paths, the compare list and the public diff cut): toolchain sha check, two builds (byte-identical), BUILD.json record check, every one of the 35 webui test files singly, the suite, baseline manifest of the 195 paths, the same seven mutants (all seven anchors count once on the rebased files), sha256sum -c over the whole manifest, provenance under MOSAIC_REQUIRE_EDITOR_BUILD=1, sha256sum -c again, then the patch, manifest, and the public/ diff Mos reads (public.diff: all of packages/webui/src/public, which includes the generated editor.js and editor.css; public-handwritten.diff: bus.js and index.html only) with sha256 values.
Rebase round on 7bd2a348c (row 125 = aa625565f, row 162 = 7e7f8e208), run 2026-10-18T20:42Z–20:48Z
Supersedes the "Rebase onto df4d5702f … nothing run" section above: sage-0897 moved the base to 7bd2a348c (row 162 landed on top of df4d5702f) before the run, so I rebuilt the tree there instead of using the df4d5702f tree. Nothing was run on df4d5702f.
Tree. Detached worktree at 7bd2a348c, git apply --3way --index r3/candidate.patch: exit 0, 0 unmerged, the same 195 paths as r3 (rebase-7bd2a348c/changed-paths.txt is byte-equal to r3/changed-paths.txt). Against r3's manifest, three files differ: docs/TOOLS.md, packages/webui/src/public/bus.js, packages/webui/src/serve.mjs. The bus.js and serve.mjs results are byte-identical to the df4d5702f tree. Row 162 touched only one of my 195 paths, docs/TOOLS.md (its edits are around lines 342-384; my five-line row 154 paragraph is at line 441, a separate region). My TOOLS.md added lines are identical in the df4d5702f and 7bd2a348c trees. The editor sources, editor.js, editor.css and editor/BUILD.json are unchanged from r3.
Run (rebase-7bd2a348c/run.sh, run.out; the busy check ran first and printed CLEAR; one run, no rerun). Counts are awk sums over the logs, not the script's own line:
- Rebuild twice: both exit 0, byte-identical
editor.jsandBUILD.json;BUILD.jsonrecordseditor.js9c881b13…fee, 758420 bytes, as in r3. - Every webui test file singly: 35 of 35 files, 214 tests, 214 pass, 0 fail, 0 skipped; no file reports zero tests. The suite run: 214 / 214 / 0 / 0.
browser.test.mjs:22still pins['/editor.js', '/brand.js', '/app.js', '/markdown-preview.js', '/chat.js', '/bus.js'], andindex.htmlloads them in that order. Neither 125 nor 162 touched either file.single-browser.log: 1 test, 1 pass.- Seven mutants, each exit 1 with
BUILD.jsonbacked up and restored around it. Killers: gate-true and docshown-revert by "a file the editor cannot write back byte for byte opens as Source…" (1 fail each); serialiser-verbatim by the link-title, image-alt and image-title line-break tests (3); info-verbatim by the code-block info line-break test (1); g5-backtick-kept by the backtick info-string test (1); link-backslash by the two link-title backslash tests (2); image-backslash by the two image-title backslash tests (2). As in r3, the two backslash mutants fail on the exact-string assert; the re-read assert is not reached. - After the mutants:
sha256sum -cover the 195-entry baseline manifest, 195 OK, 0 failures (post-mutant-check.txt,pre-cut-check.txt); editor-provenance withMOSAIC_REQUIRE_EDITOR_BUILD=1: 6 tests, 6 pass. - Cut last: path list equal to r3's,
candidate.patchapplies to a clean export of7bd2a348c, andsha256sum -cover the new manifest is 195 OK of 195 (cut-manifest-check.txt).
Digests.
candidate.patcha822401ac794252eaebc08510a89597d87ff254508baa3830f7360314cefffc9candidate-manifest.sha25641024ac5e2b118e5ff11a74b61ee82e5bc9f177d0622c5e5af0f284622985111public.diff3b67cf23b70f4e753cda610db0a24c4eaa3107713dd9e0eace9e49a08b78663b(4 files:bus.js,editor.css,editor.js,index.html)public-handwritten.diff2251d83b03d467333ca21ad7bbc463040701f4ead1e2b7d01fc289506a98476e(bus.jsandindex.htmlonly; for Mos's decision 123 go,editor.jsandeditor.cssare generated and covered by theBUILD.jsonrecord plus the byte-identical double build)
Not tested. Nothing live: no pinned-copy re-pin, no Console restart. The run is in a scratch worktree only.
Rebase round on d88613bac (row 155 landed as b2f57ca70), run 2026-10-18T21:24Z–21:36Z
Supersedes the 7bd2a348c round above as the candidate to review; that round's directory rebase-7bd2a348c/ stays as the record of what was run on the earlier base. Base moved because row 155 landed (sage-0909). The packet has two run directories for this base, kept apart:
rebase-d88613bac-stopped/— first run, 21:24:11Z–21:28:08Z. Stopped at the full webui suite, exit 1: 221 tests, 220 pass, 1 fail. The failure istests/board-held.test.mjs:163("a redirect answer on a Board read…"), "Condition timed out:!document.querySelector("#signed-out").hidden" fromtests/browser.mjs:48, atboard-held.test.mjs:220, after 11.9 s. Singles in that run: 36 of 36 files, 221 pass, 0 fail. Left untouched.rebase-d88613bac/— second run, once, script unchanged, no test change (sage-0919). Passed end to end.
Sage's account of the failure (sage-0919): the row 155 r3 gate at 21:10Z failed on the same test at the same :220 wait after 13.9 s, on d88613bac's webui without my patch, and passed on rerun; the helper at board-held.test.mjs:76 polls 100 times at 50 ms (about 5 s) and the sign-out after the redirect answer runs slower than that under a parallel suite; filed as P19 366. I did not test that explanation myself: I ran no clean-base suite. What I observed is the failure once in the full suite, never in the singles, and a pass in the rerun.
Tree. Detached worktree at d88613bac, git apply --3way --index rebase-7bd2a348c/candidate.patch: exit 0, 0 unmerged, same 195 paths (changed-paths.txt byte-equal to the earlier round's). Four of my paths differ from the 7bd2a348c tree: docs/TOOLS.md, packages/webui/README.md, public/bus.js, serve.mjs. For the first three the changed lines equal 155's own diff to that file; for serve.mjs I read the combined hunk by hand: my two static-map lines (/editor.js, /editor.css) sit above /icons.svg, none of 155's lines overlap. 155's changes to webui/src/bus.mjs, cli.mjs, bus/src/broker.mjs and tests are not in my paths. The mutant anchors in bus.js still occur once.
Run (second run; rebase-d88613bac/run.sh, run.out; busy check CLEAR first). Counts are awk sums over the logs:
- Rebuild twice: byte-identical
editor.jsandBUILD.json;BUILD.jsonrecordseditor.js9c881b13…fee, 758420 bytes, as in r3. - Singles: 36 of 36 files, 221 tests, 221 pass, 0 fail, 0 skipped, 0 cancelled (155 added
assistant.test.mjs). - Full suite: 221 tests, 221 pass, 0 fail.
packages/bus/tests/channel-proof.test.mjs, whole file (sage-0909; 164's byte freeze pinswebui/src/bus.mjs,human.mjs,human-cli.mjs,cli/src/transport.mjs): 2 tests, 2 pass.browser.test.mjs:22still pins['/editor.js', '/brand.js', '/app.js', '/markdown-preview.js', '/chat.js', '/bus.js']; browser single 1 of 1.- Seven mutants, each exit 1,
BUILD.jsonrestored around each. Failing counts: gate-true 1, docshown-revert 1, serialiser-verbatim 3, info-verbatim 1, g5-backtick-kept 1, link-backslash 2, image-backslash 2; killers are the same tests as in the earlier rounds. sha256sum -c: post-mutant 195 OK, pre-cut 195 OK, new manifest 195 OK, no failures. Editor-provenance withMOSAIC_REQUIRE_EDITOR_BUILD=1after the mutants: 6 tests, 6 pass.- The patch applies to a clean export of
d88613bac.
Digests.
candidate.patchbdd046abbeca2884581c43175b55ed367f454927f8271a75b32ae3cb20b3ac6bcandidate-manifest.sha2563a4bc21c9a8170667b0b36503195bf631b735f5e728ab7e9c9cd8974ccce3b05public.diff902f02448ea7924524432f2b7431e064c3d6e69413ec277a27e883010305505b(bus.js,editor.css,editor.js,index.html)public-handwritten.diffea59079ee44ddbc53af0534f7c261b9fd05c7f35ab715ba58a5db2ac9276ec75(bus.js,index.html)
Not tested. Nothing live: no re-pin, no Console restart. The row's in-review record still names the 7bd2a348c manifest 41024ac5…5111; I made no queue move this round (sage-0905: leave the row in-review).