Notes editor: first edit of a non-canonical Note is lost to a (conflict) copy #1618

Closed
opened 2026-10-12 16:10:59 +00:00 by kayg · 7 comments
Owner

Severity: DATA LOSS. Production is affected.

Found by the Notes block editor hunt (owner focus, 2026-10-12). Repro scripts are on dev (6fefd4a62) in apps/web/e2e/break-editor/. Run from apps/web after bun run build: node e2e/break-editor/<script>. Exit 1 = reproduced, 0 = fixed. Server used: prebuilt rev 8e8409629 (same as production). The scripts leave calternal-server-release processes behind; kill them after each run.

Symptom

The first edit of a Note whose Markdown is not in the editor's canonical form is not saved. The editor snaps back within about 7 s and a "

**Severity: DATA LOSS. Production is affected.** Found by the Notes block editor hunt (owner focus, 2026-10-12). Repro scripts are on `dev` (6fefd4a62) in `apps/web/e2e/break-editor/`. Run from `apps/web` after `bun run build`: `node e2e/break-editor/<script>`. Exit 1 = reproduced, 0 = fixed. Server used: prebuilt rev 8e8409629 (same as production). The scripts leave `calternal-server-release` processes behind; kill them after each run. ## Symptom The first edit of a Note whose Markdown is not in the editor's canonical form is not saved. The editor snaps back within about 7 s and a "<title> (conflict)" Note appears holding the edit. Forms that trigger it: - a heading or paragraph directly followed by another block with no blank line between; - `[!NOTE]` upper-case callout type; - `[!tip]-` folded callout; - a lazy continuation line in a quote; - an extra `---` block. Scripts: `09-noncanonical-markdown-edit-forks-note.mjs` (3/3) and `10-conflict-matrix.mjs` (40 source forms, about 5 min, run alone). Notes written by hand, by other apps, by import or by WebDAV clients are often in these forms, so this hits real data. ## Lead DESIGN §9: Markdown is the truth; the Yrs doc is a cache rebuilt from .md. The Rust converter (`crates/calternal-collab/src/markdown.rs`) and the TypeScript one must agree byte for byte. A first edit seems to re-serialise the whole Note canonically; the server sees the file differ from what it expected and forks a conflict copy (session.rs conflict shadow, around line 2410). Normalising the source must never count as a conflict, and must not rewrite parts of the file the User did not touch beyond what the canonical form needs. ## Done when - Script 09 exits 0 three runs in a row; the matrix (10) shows no fork for any form. - Rust and TypeScript converter tests for each form above, byte-identical output.
Author
Owner

Started on job/noncanon-1618, base c94c922213. Reading the #1616/#1617 splice fix and tracing noncanonical source spans against checked flush validation. Will add failing persistence regressions before changing the splice and run the requested focused browser scenarios.

Started on job/noncanon-1618, base c94c922213627a95f946e80f240caebc4c620c3c. Reading the #1616/#1617 splice fix and tracing noncanonical source spans against checked flush validation. Will add failing persistence regressions before changing the splice and run the requested focused browser scenarios.
Author
Owner

Root cause reproduced: all seven persistence cases fail before the fix and retain the pre-edit Markdown; the shared byte-splice case returns None. parse_document in crates/calternal-collab/src/markdown.rs records each empty paragraph in a blank run as the same start..i range. patch_source_blocks rejects those overlapping spans. Noncanonical source needs that fallback, and flush_document_locked in session.rs treats rejection as an unmappable edit and preserves a conflict copy. The fix assigns each empty paragraph its own blank source line. TypeScript already passes all seven shared source/canonical/saved-byte cases (161 Markdown tests passed); no TypeScript production change is needed.

Root cause reproduced: all seven persistence cases fail before the fix and retain the pre-edit Markdown; the shared byte-splice case returns None. parse_document in crates/calternal-collab/src/markdown.rs records each empty paragraph in a blank run as the same start..i range. patch_source_blocks rejects those overlapping spans. Noncanonical source needs that fallback, and flush_document_locked in session.rs treats rejection as an unmappable edit and preserves a conflict copy. The fix assigns each empty paragraph its own blank source line. TypeScript already passes all seven shared source/canonical/saved-byte cases (161 Markdown tests passed); no TypeScript production change is needed.
Author
Owner

Atomic fix committed: 213e881e7. After fetching origin/dev (already up to date), cargo fmt --check, cargo clippy -p calternal-collab --all-targets -- -D warnings, and cargo test -p calternal-collab all exit 0. Unit suite: test result: ok. 152 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 190.65s. Existing external/concurrent edit, restart, untouched bytes and cross-language integrations pass too. Web: svelte-check found 0 errors and 0 warnings; Test Files 324 passed (324); Tests 2366 passed (2366). Building the branch server now for 09 x3, matrix 10, delete 20 and Undo 19. Server PIDs will be recorded and checked after every run.

Atomic fix committed: 213e881e7. After fetching origin/dev (already up to date), cargo fmt --check, cargo clippy -p calternal-collab --all-targets -- -D warnings, and cargo test -p calternal-collab all exit 0. Unit suite: test result: ok. 152 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 190.65s. Existing external/concurrent edit, restart, untouched bytes and cross-language integrations pass too. Web: svelte-check found 0 errors and 0 warnings; Test Files 324 passed (324); Tests 2366 passed (2366). Building the branch server now for 09 x3, matrix 10, delete 20 and Undo 19. Server PIDs will be recorded and checked after every run.
Author
Owner

The requested browser round passed: 09 three consecutive exits 0; matrix 10 exit 0 with 40/40 forms and no skips; 20 and 19 each exit 0. All six server PIDs stopped. During final review, the existing unmappable_source_keeps_edits_in_conflict_copy test exposed another single-editor violation of the issue rule: converting an adjacent heading into a paragraph forked despite an unchanged outside revision. Added a shared Rust/TypeScript byte case; both failed before the boundary fix. Both writers now first try the minimal splice, then retry only the separators beside touched blocks if semantic validation rejects it. Neighbour prose stays verbatim. Two existing expectations intentionally change under #1618: the single-editor conflict-copy test now requires the original Note and no sibling; the app-body convergence test now expects this conversion to be patchable. Concurrent/outside-edit expectations are unchanged. Re-running gates because this is an additional production change, then checking the conversion in a real browser.

The requested browser round passed: 09 three consecutive exits 0; matrix 10 exit 0 with 40/40 forms and no skips; 20 and 19 each exit 0. All six server PIDs stopped. During final review, the existing unmappable_source_keeps_edits_in_conflict_copy test exposed another single-editor violation of the issue rule: converting an adjacent heading into a paragraph forked despite an unchanged outside revision. Added a shared Rust/TypeScript byte case; both failed before the boundary fix. Both writers now first try the minimal splice, then retry only the separators beside touched blocks if semantic validation rejects it. Neighbour prose stays verbatim. Two existing expectations intentionally change under #1618: the single-editor conflict-copy test now requires the original Note and no sibling; the app-body convergence test now expects this conversion to be patchable. Concurrent/outside-edit expectations are unchanged. Re-running gates because this is an additional production change, then checking the conversion in a real browser.
Author
Owner

Completed #1618 on job/noncanon-1618. Head: 55486911ff. Base: c94c922213. Fetched origin/dev once before gates; merge reported Already up to date. No push, deploy or issue close. Worktree is clean.

Built: disjoint source spans for empty paragraphs, checked separator repair beside touched blocks, seven first-edit persistence regressions, nine shared Rust/TypeScript byte cases (including LF/CRLF block conversion), and a 40-form browser matrix. Untouched block text and line endings stay verbatim.

Root cause: crates/calternal-collab/src/markdown.rs:1834 captured every empty paragraph in one blank run as the same start..i range. patch_source_blocks rejected the overlapping spans at markdown.rs:3782. Noncanonical Markdown needed that fallback, and session.rs:2348 treated its rejection as an unmappable edit and preserved a sibling Note. All seven first-edit persistence regressions and the shared byte-splice regression failed before the span fix. A second cause was a one-line boundary that ceased to separate blocks after a heading became a paragraph; Rust forked and TypeScript returned null. Both writers now try the minimal splice first, then one validated separator repair at touched boundaries.

Files:

  • apps/web/e2e/break-editor/10-conflict-matrix.mjs
  • contracts/vectors/markdown/README.md
  • contracts/vectors/markdown/noncanonical-edit.v1.json
  • crates/calternal-collab/src/markdown.rs
  • crates/calternal-collab/src/session.rs
  • packages/editor/src/markdown.test.ts
  • packages/editor/src/markdown.ts

Commits:

  • 213e881e7: disjoint blank paragraph spans and first-edit regressions.
  • 9ad393ea5: add adjacent headings to the matrix (it had 39 forms, not 40).
  • d58532a09: clarify shared fixture/test documentation.
  • 55486911f: repair touched boundaries in both writers and verify real outside-write adoption.

Changed existing expectations, as required by #1618: unmappable_source_keeps_edits_in_conflict_copy now requires a saved conversion in the original Note and no sibling; the app-body convergence test now requires the same conversion to be patchable. The delayed conflict-copy adoption fixture now makes a real outside write at FlushAfterSnapshot; its original late-insert assertions are unchanged, and an added assertion requires the preserved sibling to exist. All real concurrent/outside-edit, delayed-update, restart and cross-language tests pass.

Gates (final successful runs; output excerpts below are verbatim). Rust environment: CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, TMPDIR=/target/tmp; preset CARGO_TARGET_DIR was not changed.

cargo fmt --check: exit 0, no output.

cargo clippy -p calternal-collab --all-targets -- -D warnings: exit 0.

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.54s

cargo test -p calternal-collab -- --test-threads=4: exit 0 (152 unit tests, 68 integration tests).

test result: ok. 152 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 102.06s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.47s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.51s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.37s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 36.74s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.15s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.91s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.36s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.54s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.13s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.02s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.90s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 18.23s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.70s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 16.79s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cd apps/web && bun run check: exit 0.

perf-lint: PASS; 0 violations; 20352 scoped exceptions
svelte-check found 0 errors and 0 warnings

cd apps/web && bun run test -- --maxWorkers=2: exit 0.

 Test Files  324 passed (324)
      Tests  2366 passed (2366)
   Duration  262.04s (environment 28%, transform 25%, import 22%, tests 20%, setup 5%)

cd packages/editor && bunx vitest run src/markdown.test.ts --maxWorkers=2: exit 0.

 Test Files  1 passed (1)
      Tests  163 passed (163)
   Duration  1.52s (transform 246ms, setup 35ms, import 268ms, tests 164ms, environment 895ms)

bun install, cd apps/web && bun run build and cargo build -p calternal-server: exit 0. Browser helpers used this worktree’s server through the PID-recording wrapper, rather than the shared prebuilt server. After the extra boundary fix, the web and server builds were refreshed; the final server build matched the web build identity.

Requested browser round: script 09 x3, matrix 10 once, script 20 once and script 19 once all exited 0. This round verified the span fix; the additional boundary repair then received its own final-build browser check below. MAC=1 was used except for script 19, whose supplied shortcut is literal Control+z. Browser plugin unavailable; used the repo Chromium/Playwright harness.

e2e-09-1.log (verbatim output, setup URL diagnostic omitted):

heading-then-paragraph: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork heading-then-paragraph"]
paragraph-then-list: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork paragraph-then-list"]
adjacent-headings: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork adjacent-headings"]
no defect observed (noncanonical-edit-forks-note)
--- console/network lines: 0

e2e-09-2.log (verbatim output, setup URL diagnostic omitted):

heading-then-paragraph: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork heading-then-paragraph"]
paragraph-then-list: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork paragraph-then-list"]
adjacent-headings: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork adjacent-headings"]
no defect observed (noncanonical-edit-forks-note)
--- console/network lines: 0

e2e-09-3.log (verbatim output, setup URL diagnostic omitted):

heading-then-paragraph: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork heading-then-paragraph"]
paragraph-then-list: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork paragraph-then-list"]
adjacent-headings: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork adjacent-headings"]
no defect observed (noncanonical-edit-forks-note)
--- console/network lines: 0

e2e-10.log (verbatim output, setup URL diagnostic omitted):

ok        canonical
ok        crlf
ok        star-bullets
ok        plus-bullets
ok        paren-numbered
ok        numbered-from-3
ok        nested-4space
ok        nested-tab
ok        loose-list
ok        list-after-para
ok        heading-no-blank
ok        adjacent-headings
ok        setext-h2
ok        setext-h1
ok        hr-star
ok        hr-underscore
ok        hard-break-spaces
ok        hard-break-backslash
ok        trailing-spaces
ok        table-unpadded
ok        table-aligned
ok        code-tilde
ok        code-indented
ok        code-no-lang
ok        callout-collapsed
ok        callout-lower
ok        quote-lazy
ok        emphasis-underscore
ok        task-uppercase-x
ok        task-star
ok        html-block
ok        footnote
ok        multiple-blank-lines
ok        bare-url
ok        autolink
ok        image-ref
ok        link-title
ok        escaped
ok        emoji-rtl
ok        frontmatter-extra
no defect observed (matrix)
--- console/network lines: 0

e2e-20.log (verbatim output, setup URL diagnostic omitted):

menu     after 0.4 s [Para A,Para C] after 6 s [Para A,Para C] disk has Para B: false notes: ["BD menu"]
select   after 0.4 s [Para A,Para C] after 6 s [Para A,Para C] disk has Para B: false notes: ["BD select"]
text     after 0.4 s [Para A,Para C] after 6 s [Para A,Para C] disk has Para B: false notes: ["BD text"]
session  after 0.4 s [Para A,Para B,Para C] after 6 s [Para A,Para B,Para C] disk has Para NEW: false notes: ["BD session"]
no defect observed (block-delete)
--- console/network lines: 0

e2e-19.log (verbatim output, setup URL diagnostic omitted):

pause 300  dup [Para A,Para B,Para B,Para C] -> Ctrl+Z [Para A,Para B,Para C] -> 4.5 s later [Para A,Para B,Para C] disk ,# DupUndo 300,Para A,Para B,Para C,
pause 900  dup [Para A,Para B,Para B,Para C] -> Ctrl+Z [Para A,Para B,Para C] -> 4.5 s later [Para A,Para B,Para C] disk ,# DupUndo 900,Para A,Para B,Para C,
pause 1500 dup [Para A,Para B,Para B,Para C] -> Ctrl+Z [Para A,Para B,Para C] -> 4.5 s later [Para A,Para B,Para C] disk ,# DupUndo 1500,Para A,Para B,Para C,
pause 3000 dup [Para A,Para B,Para B,Para C] -> Ctrl+Z [Para A,Para B,Para C] -> 4.5 s later [Para A,Para B,Para C] disk ,# DupUndo 3000,Para A,Para B,Para C,
no defect observed (duplicate-undo)
--- console/network lines: 0

boundary-browser-final.log (verbatim output, setup URL diagnostic omitted):

heading-to-paragraph: editorHasEdit=true diskHasRequiredSeparator=true notesWithThisTitle=1
capture 390 light macOS: loaded Note and saved edit
capture 390 dark macOS: loaded Note and saved edit
capture 820 light macOS: loaded Note and saved edit
capture 820 dark macOS: loaded Note and saved edit
capture 1440 light macOS: loaded Note and saved edit
capture 1440 dark macOS: loaded Note and saved edit
no defect observed (heading-conversion); console/network lines: 0

The first boundary capture verified saving but rejected its screenshot because it detected an update toast from mismatched build identities. The server was rebuilt after the web build, and the final capture passed with no toast. Six actual-size production captures cover 390, 820 and 1440 px, light/dark, macOS platform emulation. Captures are attached for orchestrator review; no visual styling or control was added.

390px light, macOS

390px dark, macOS

820px light, macOS

820px dark, macOS

1440px light, macOS

1440px dark, macOS

UX gaps closed: first edits no longer revert or fork for the reported source forms; heading-to-paragraph conversion inserts the required separator and keeps the edit in the original Note; block delete and saved-duplicate Undo stay fixed. UX gaps left / known gaps: none found in the requested cases. Captures and interaction checks use Chromium with macOS platform emulation, not native macOS; the owner’s Mac VM restriction was honored.

Decisions: keep source-span correction separate from the serializer contract; allow one separator-repair retry only after a minimal splice fails semantic validation, without rewriting neighbour text; add the missing adjacent-heading matrix case. The two legacy expectation changes and the real outside-race fixture are explicit consequences of #1618’s no-single-editor-conflict rule, not relaxed assertions. No new product decision.

Expected cost: O(Note bytes + blocks) with bounded parser depth; the rare boundary-repair path adds at most one semantic reparse. Ordinary successful splices still return their minimal bytes. No additional filesystem or Index IO was added. No perf measurement was run because the current verification policy limits those runs to performance issues.

Cleanup: all recorded server PIDs stopped: 2793237, 2799119, 2804398, 2809483, 2851328, 2858301, 2902551, 2908572. cargo clean output (verbatim):

Removed 16260 files, 17.2GiB total

Deleted apps/web/build and apps/web/.svelte-kit/output. Screenshots and logs remain only under ignored artifacts/; none were committed. Re-read changed module/function documentation.

For the merge round: no requested verification was deferred. Run the normal combined-branch gates and acceptance work on the merged result.

Completed #1618 on job/noncanon-1618. Head: 55486911ff5513466a80bfcd056d0e29a002a72a. Base: c94c922213627a95f946e80f240caebc4c620c3c. Fetched origin/dev once before gates; merge reported `Already up to date.` No push, deploy or issue close. Worktree is clean. Built: disjoint source spans for empty paragraphs, checked separator repair beside touched blocks, seven first-edit persistence regressions, nine shared Rust/TypeScript byte cases (including LF/CRLF block conversion), and a 40-form browser matrix. Untouched block text and line endings stay verbatim. Root cause: `crates/calternal-collab/src/markdown.rs:1834` captured every empty paragraph in one blank run as the same `start..i` range. `patch_source_blocks` rejected the overlapping spans at `markdown.rs:3782`. Noncanonical Markdown needed that fallback, and `session.rs:2348` treated its rejection as an unmappable edit and preserved a sibling Note. All seven first-edit persistence regressions and the shared byte-splice regression failed before the span fix. A second cause was a one-line boundary that ceased to separate blocks after a heading became a paragraph; Rust forked and TypeScript returned null. Both writers now try the minimal splice first, then one validated separator repair at touched boundaries. Files: - `apps/web/e2e/break-editor/10-conflict-matrix.mjs` - `contracts/vectors/markdown/README.md` - `contracts/vectors/markdown/noncanonical-edit.v1.json` - `crates/calternal-collab/src/markdown.rs` - `crates/calternal-collab/src/session.rs` - `packages/editor/src/markdown.test.ts` - `packages/editor/src/markdown.ts` Commits: - 213e881e7: disjoint blank paragraph spans and first-edit regressions. - 9ad393ea5: add adjacent headings to the matrix (it had 39 forms, not 40). - d58532a09: clarify shared fixture/test documentation. - 55486911f: repair touched boundaries in both writers and verify real outside-write adoption. Changed existing expectations, as required by #1618: `unmappable_source_keeps_edits_in_conflict_copy` now requires a saved conversion in the original Note and no sibling; the app-body convergence test now requires the same conversion to be patchable. The delayed conflict-copy adoption fixture now makes a real outside write at `FlushAfterSnapshot`; its original late-insert assertions are unchanged, and an added assertion requires the preserved sibling to exist. All real concurrent/outside-edit, delayed-update, restart and cross-language tests pass. Gates (final successful runs; output excerpts below are verbatim). Rust environment: CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4, TMPDIR=<worktree>/target/tmp; preset CARGO_TARGET_DIR was not changed. `cargo fmt --check`: exit 0, no output. `cargo clippy -p calternal-collab --all-targets -- -D warnings`: exit 0. ``` Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.54s ``` `cargo test -p calternal-collab -- --test-threads=4`: exit 0 (152 unit tests, 68 integration tests). ``` test result: ok. 152 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 102.06s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.47s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.51s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.37s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 36.74s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.15s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.91s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.36s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.54s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.13s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.02s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.90s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 18.23s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.70s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 16.79s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cd apps/web && bun run check`: exit 0. ``` perf-lint: PASS; 0 violations; 20352 scoped exceptions svelte-check found 0 errors and 0 warnings ``` `cd apps/web && bun run test -- --maxWorkers=2`: exit 0. ``` Test Files 324 passed (324) Tests 2366 passed (2366) Duration 262.04s (environment 28%, transform 25%, import 22%, tests 20%, setup 5%) ``` `cd packages/editor && bunx vitest run src/markdown.test.ts --maxWorkers=2`: exit 0. ``` Test Files 1 passed (1) Tests 163 passed (163) Duration 1.52s (transform 246ms, setup 35ms, import 268ms, tests 164ms, environment 895ms) ``` `bun install`, `cd apps/web && bun run build` and `cargo build -p calternal-server`: exit 0. Browser helpers used this worktree’s server through the PID-recording wrapper, rather than the shared prebuilt server. After the extra boundary fix, the web and server builds were refreshed; the final server build matched the web build identity. Requested browser round: script 09 x3, matrix 10 once, script 20 once and script 19 once all exited 0. This round verified the span fix; the additional boundary repair then received its own final-build browser check below. MAC=1 was used except for script 19, whose supplied shortcut is literal Control+z. Browser plugin unavailable; used the repo Chromium/Playwright harness. `e2e-09-1.log` (verbatim output, setup URL diagnostic omitted): ``` heading-then-paragraph: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork heading-then-paragraph"] paragraph-then-list: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork paragraph-then-list"] adjacent-headings: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork adjacent-headings"] no defect observed (noncanonical-edit-forks-note) --- console/network lines: 0 ``` `e2e-09-2.log` (verbatim output, setup URL diagnostic omitted): ``` heading-then-paragraph: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork heading-then-paragraph"] paragraph-then-list: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork paragraph-then-list"] adjacent-headings: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork adjacent-headings"] no defect observed (noncanonical-edit-forks-note) --- console/network lines: 0 ``` `e2e-09-3.log` (verbatim output, setup URL diagnostic omitted): ``` heading-then-paragraph: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork heading-then-paragraph"] paragraph-then-list: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork paragraph-then-list"] adjacent-headings: typedInEditor=true editorStillHasEdit=true diskHasEdit=true notesWithThisTitle=["Fork adjacent-headings"] no defect observed (noncanonical-edit-forks-note) --- console/network lines: 0 ``` `e2e-10.log` (verbatim output, setup URL diagnostic omitted): ``` ok canonical ok crlf ok star-bullets ok plus-bullets ok paren-numbered ok numbered-from-3 ok nested-4space ok nested-tab ok loose-list ok list-after-para ok heading-no-blank ok adjacent-headings ok setext-h2 ok setext-h1 ok hr-star ok hr-underscore ok hard-break-spaces ok hard-break-backslash ok trailing-spaces ok table-unpadded ok table-aligned ok code-tilde ok code-indented ok code-no-lang ok callout-collapsed ok callout-lower ok quote-lazy ok emphasis-underscore ok task-uppercase-x ok task-star ok html-block ok footnote ok multiple-blank-lines ok bare-url ok autolink ok image-ref ok link-title ok escaped ok emoji-rtl ok frontmatter-extra no defect observed (matrix) --- console/network lines: 0 ``` `e2e-20.log` (verbatim output, setup URL diagnostic omitted): ``` menu after 0.4 s [Para A,Para C] after 6 s [Para A,Para C] disk has Para B: false notes: ["BD menu"] select after 0.4 s [Para A,Para C] after 6 s [Para A,Para C] disk has Para B: false notes: ["BD select"] text after 0.4 s [Para A,Para C] after 6 s [Para A,Para C] disk has Para B: false notes: ["BD text"] session after 0.4 s [Para A,Para B,Para C] after 6 s [Para A,Para B,Para C] disk has Para NEW: false notes: ["BD session"] no defect observed (block-delete) --- console/network lines: 0 ``` `e2e-19.log` (verbatim output, setup URL diagnostic omitted): ``` pause 300 dup [Para A,Para B,Para B,Para C] -> Ctrl+Z [Para A,Para B,Para C] -> 4.5 s later [Para A,Para B,Para C] disk ,# DupUndo 300,Para A,Para B,Para C, pause 900 dup [Para A,Para B,Para B,Para C] -> Ctrl+Z [Para A,Para B,Para C] -> 4.5 s later [Para A,Para B,Para C] disk ,# DupUndo 900,Para A,Para B,Para C, pause 1500 dup [Para A,Para B,Para B,Para C] -> Ctrl+Z [Para A,Para B,Para C] -> 4.5 s later [Para A,Para B,Para C] disk ,# DupUndo 1500,Para A,Para B,Para C, pause 3000 dup [Para A,Para B,Para B,Para C] -> Ctrl+Z [Para A,Para B,Para C] -> 4.5 s later [Para A,Para B,Para C] disk ,# DupUndo 3000,Para A,Para B,Para C, no defect observed (duplicate-undo) --- console/network lines: 0 ``` `boundary-browser-final.log` (verbatim output, setup URL diagnostic omitted): ``` heading-to-paragraph: editorHasEdit=true diskHasRequiredSeparator=true notesWithThisTitle=1 capture 390 light macOS: loaded Note and saved edit capture 390 dark macOS: loaded Note and saved edit capture 820 light macOS: loaded Note and saved edit capture 820 dark macOS: loaded Note and saved edit capture 1440 light macOS: loaded Note and saved edit capture 1440 dark macOS: loaded Note and saved edit no defect observed (heading-conversion); console/network lines: 0 ``` The first boundary capture verified saving but rejected its screenshot because it detected an update toast from mismatched build identities. The server was rebuilt after the web build, and the final capture passed with no toast. Six actual-size production captures cover 390, 820 and 1440 px, light/dark, macOS platform emulation. Captures are attached for orchestrator review; no visual styling or control was added. ![390px light, macOS](https://git.kayg.org/attachments/bc37dc71-4e11-4f6f-88c5-c147e49f26b5) ![390px dark, macOS](https://git.kayg.org/attachments/ad080a5e-c21c-4ba9-891a-119a7d2d5e9e) ![820px light, macOS](https://git.kayg.org/attachments/4a69b3f6-3076-4ee2-a680-66072f3d9d69) ![820px dark, macOS](https://git.kayg.org/attachments/65de3879-ebbb-471c-929e-ebdcbb941244) ![1440px light, macOS](https://git.kayg.org/attachments/cf42881c-ec54-44b3-878e-b028372b7e3b) ![1440px dark, macOS](https://git.kayg.org/attachments/424b43ea-559c-4baa-ba8d-182fbcc619dc) UX gaps closed: first edits no longer revert or fork for the reported source forms; heading-to-paragraph conversion inserts the required separator and keeps the edit in the original Note; block delete and saved-duplicate Undo stay fixed. UX gaps left / known gaps: none found in the requested cases. Captures and interaction checks use Chromium with macOS platform emulation, not native macOS; the owner’s Mac VM restriction was honored. Decisions: keep source-span correction separate from the serializer contract; allow one separator-repair retry only after a minimal splice fails semantic validation, without rewriting neighbour text; add the missing adjacent-heading matrix case. The two legacy expectation changes and the real outside-race fixture are explicit consequences of #1618’s no-single-editor-conflict rule, not relaxed assertions. No new product decision. Expected cost: O(Note bytes + blocks) with bounded parser depth; the rare boundary-repair path adds at most one semantic reparse. Ordinary successful splices still return their minimal bytes. No additional filesystem or Index IO was added. No perf measurement was run because the current verification policy limits those runs to performance issues. Cleanup: all recorded server PIDs stopped: 2793237, 2799119, 2804398, 2809483, 2851328, 2858301, 2902551, 2908572. `cargo clean` output (verbatim): ``` Removed 16260 files, 17.2GiB total ``` Deleted apps/web/build and apps/web/.svelte-kit/output. Screenshots and logs remain only under ignored artifacts/; none were committed. Re-read changed module/function documentation. For the merge round: no requested verification was deferred. Run the normal combined-branch gates and acceptance work on the merged result.
Author
Owner

Final-report line reference correction: at head 55486911ff the overlap guard is crates/calternal-collab/src/markdown.rs:3784 (not 3782). The blank-run capture is markdown.rs:1834; flush fallback is session.rs:2348; bounded separator repair is markdown.rs:3921 and packages/editor/src/markdown.ts:2083. All six captures are attached, all eight recorded server PIDs are stopped, cargo clean removed 17.2 GiB, web build output is deleted, and the worktree is clean.

Final-report line reference correction: at head 55486911ff5513466a80bfcd056d0e29a002a72a the overlap guard is crates/calternal-collab/src/markdown.rs:3784 (not 3782). The blank-run capture is markdown.rs:1834; flush fallback is session.rs:2348; bounded separator repair is markdown.rs:3921 and packages/editor/src/markdown.ts:2083. All six captures are attached, all eight recorded server PIDs are stopped, cargo clean removed 17.2 GiB, web build output is deleted, and the worktree is clean.
Author
Owner

Deployed to calternal.cloud in ebaa86165. Gates: fmt ok, clippy ok, calternal-collab 220 passed 0 failed, web check ok. Job proof: script 09 3/3 exit 0, matrix 40/40, scripts 20 and 19 exit 0.

Deployed to calternal.cloud in ebaa86165. Gates: fmt ok, clippy ok, calternal-collab 220 passed 0 failed, web check ok. Job proof: script 09 3/3 exit 0, matrix 40/40, scripts 20 and 19 exit 0.
kayg closed this issue 2026-10-12 19:44:34 +00:00
Commenting is not possible because the repository is archived.
No labels
No milestone
No project
No assignees
1 participant
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/calternal-v0#1618
No description provided.