Notes editor: WebKit stalls on link actions during fuzzing (hang or harness?) #1371

Closed
opened 2026-10-07 14:26:25 +00:00 by kayg · 8 comments
Owner

From the editor fuzz round 2 (#1335, branch job/editfuzz-1335 head 3027b98af, raw logs in that worktree's ignored artifacts/editfuzz/): Playwright WebKit workers stalled on link-related actions (create link by shortcut/toolbar, paste URL over a selection, edit or remove a link); Chromium did not stall on the same actions. A stall may be a harness wait that never resolves, or a real editor hang in WebKit (Safari engine), for example a link popover or preview card that steals focus, a selection loop, or a never-settling save.
Determine which, with a minimal manual reproduction in Playwright WebKit (and Safari on the macOS VM if needed): for each link action, record whether the page stays responsive (evaluate a script within 1 s), whether the editor accepts typing afterwards, and any console errors. If it is a product bug: fix it with a regression test. Also check the owner's report from the same period: several link icons appearing for one link in the editor (duplicate or stale link decorations/preview icons after edits).

From the editor fuzz round 2 (#1335, branch job/editfuzz-1335 head 3027b98af, raw logs in that worktree's ignored artifacts/editfuzz/): Playwright WebKit workers stalled on link-related actions (create link by shortcut/toolbar, paste URL over a selection, edit or remove a link); Chromium did not stall on the same actions. A stall may be a harness wait that never resolves, or a real editor hang in WebKit (Safari engine), for example a link popover or preview card that steals focus, a selection loop, or a never-settling save. Determine which, with a minimal manual reproduction in Playwright WebKit (and Safari on the macOS VM if needed): for each link action, record whether the page stays responsive (evaluate a script within 1 s), whether the editor accepts typing afterwards, and any console errors. If it is a product bug: fix it with a regression test. Also check the owner's report from the same period: several link icons appearing for one link in the editor (duplicate or stale link decorations/preview icons after edits).
Author
Owner

Starting #1371 on job/wklink-1371; base HEAD 0419170fe4 (origin/dev). Reproducing the WebKit link actions and comparing Chromium before making any editor or harness change.

Starting #1371 on job/wklink-1371; base HEAD 0419170fe456b58614befcb12d9c9d255118f1c0 (origin/dev). Reproducing the WebKit link actions and comparing Chromium before making any editor or harness change.
Author
Owner

Deterministic WebKit replay now passes shortcut creation, toolbar creation, selected-word URL paste, adjacent links, and wrapped-link create/Undo/Redo. Replacing the wrapped link's selected text with edited link label leaves the link anchor text as edited; the renderer probe still responds (38 ms JavaScript evaluation, 79 ms typing, 139 ms probe Undo). This is reproducible in the production build and is being compared with Chromium while I trace the shared editor link mark behavior.

Deterministic WebKit replay now passes shortcut creation, toolbar creation, selected-word URL paste, adjacent links, and wrapped-link create/Undo/Redo. Replacing the wrapped link's selected text with `edited link label` leaves the link anchor text as `edited`; the renderer probe still responds (38 ms JavaScript evaluation, 79 ms typing, 139 ms probe Undo). This is reproducible in the production build and is being compared with Chromium while I trace the shared editor link mark behavior.
Author
Owner

The deterministic WebKit and Chromium runs both found a history defect: after creating adjacent links on left and right, one Cmd+Z removes the first link as well as the second. Both links were present before Undo. The post-action JavaScript probes were 1–16 ms at this point, so this is not a renderer hang. I am tracing the shared link command's history boundaries and will add a regression check.

The deterministic WebKit and Chromium runs both found a history defect: after creating adjacent links on `left` and `right`, one Cmd+Z removes the first link as well as the second. Both links were present before Undo. The post-action JavaScript probes were 1–16 ms at this point, so this is not a renderer hang. I am tracing the shared link command's history boundaries and will add a regression check.
Author
Owner

The root cause is the live Notes history manager: it uses Yjs UndoManager, so ProseMirror's closeHistory metadata alone does not separate rapid link edits. The shared link command now stops Yjs capture and closes ProseMirror history before and after each link mutation. Focused editor tests pass: 34 tests across the collaborative history and format command suites. I am rebuilding and will verify the regression in the production WebKit and Chromium sequence.

The root cause is the live Notes history manager: it uses Yjs `UndoManager`, so ProseMirror's `closeHistory` metadata alone does not separate rapid link edits. The shared link command now stops Yjs capture and closes ProseMirror history before and after each link mutation. Focused editor tests pass: 34 tests across the collaborative history and format command suites. I am rebuilding and will verify the regression in the production WebKit and Chromium sequence.
Author
Owner

Issue #1371 — complete

Built

  • Link creation and removal now close both ProseMirror and Yjs history capture around each link command. One link gesture is one Undo/Redo step in the live Notes editor, including when links are adjacent.
  • Added collaborative-history and editor-command regression tests, plus a deterministic production WebKit/Chromium replay covering shortcut, toolbar, selected-text paste, adjacent links, wrapped links, label edit, removal, URL preview conversion, Undo/Redo, and a typing responsiveness check.
  • The replay verifies that removed links leave no anchor or preview decorations, and that a standalone URL has one preview before conversion and one link mark after conversion.

Files

  • packages/editor/src/linkCommand.ts
  • packages/editor/src/collaborationHistoryKeys.ts
  • packages/editor/src/collaborationUndo.test.ts
  • packages/editor/src/formatCommands.svelte.test.ts
  • apps/web/e2e/link-webkit-1371.mjs
  • apps/web/package.json

UX gaps closed

  • Undo now removes only the most recent adjacent link; Redo restores only that link.
  • Shortcut and toolbar creation, selected-word paste, wrapped links, label replacement, link removal, and URL-preview transitions round-trip without duplicate link decorations.
  • WebKit and Chromium both remained responsive after each action. The run captured 50 screenshots per engine at 1440×900, macOS platform emulation, in light and dark themes.

UX gap left

  • A multiword replacement at the end of a link still follows the current Tiptap Link trailing-whitespace behavior. The initial multiword experiment kept only the first word inside the link in both engines. I left that existing behavior unchanged; the regression replay replaces the label with one word and checks its Undo/Redo round-trip.

Decisions

  • DESIGN §9 does not define editor history grouping. I chose one link command per Undo/Redo step. ProseMirror closeHistory alone did not separate Notes' live Yjs UndoManager, so the command closes both capture windows on either side of the mutation.
  • The label edit uses Playwright's single text-input transaction so this check measures one replacement action, independent of time-based grouping between individual key events. Selection and link actions use the actual editor UI.
  • Each link action adds constant-time history boundaries; the existing selected-range mark operation remains the work that scales with the selected text. Renderer responsiveness probe p50/p95/max was 10/84/140 ms in WebKit and 3/14/21 ms in Chromium. These are post-action renderer probes, not end-to-end save latency.

Screenshot attachments

Each ZIP contains the original 1440×900 PNGs for its engine/theme. The three parts for each theme cover all 25 captured states.

Verification output (verbatim)

cargo build -p calternal-server --release
Finished `release` profile [optimized] target(s) in 6m 29s

cd apps/web && bun run build
✓ built in 3m 44s
Wrote site to "build"
Compressed 914 static variants; saved 21488159 bytes.

cd apps/web && bun run check
perf-lint: PASS; 0 violations; 22543 scoped exceptions
svelte-check found 0 errors and 4 warnings in 2 files

cd apps/web && bun run test
Test Files  293 passed (293)
      Tests  2103 passed (2103)
   Duration  634.83s (transform 33%, environment 23%, import 20%, tests 18%, setup 5%)

cd packages/editor && bun run test -- src/collaborationUndo.test.ts src/formatCommands.svelte.test.ts
Test Files  2 passed (2)
      Tests  34 passed (34)
   Duration  60.10s (transform 50.11s, setup 930ms, import 69.73s, tests 7.37s, environment 35.28s)

production replay
RESULT webkit {"consoleErrors":1,"pageErrors":0,"screenshots":50,"probes":27,"maxEvaluateMs":140,"typedProbes":1,"maxTypingMs":50}
CONSOLE webkit ["Viewport argument key \"interactive-widget\" not recognized and ignored."]
RESULT chromium {"consoleErrors":0,"pageErrors":0,"screenshots":50,"probes":27,"maxEvaluateMs":21,"typedProbes":1,"maxTypingMs":32}
PASS #1371 deterministic WebKit and Chromium link actions

Commits and head

  • Feature commit: 0fd948901 — fix(editor): isolate link actions in collaborative history
  • Head after merging origin/dev: fc0e8bf7fdc299ac1f7e06838e29b34ea71ee83a
# Issue #1371 — complete ## Built - Link creation and removal now close both ProseMirror and Yjs history capture around each link command. One link gesture is one Undo/Redo step in the live Notes editor, including when links are adjacent. - Added collaborative-history and editor-command regression tests, plus a deterministic production WebKit/Chromium replay covering shortcut, toolbar, selected-text paste, adjacent links, wrapped links, label edit, removal, URL preview conversion, Undo/Redo, and a typing responsiveness check. - The replay verifies that removed links leave no anchor or preview decorations, and that a standalone URL has one preview before conversion and one link mark after conversion. ## Files - `packages/editor/src/linkCommand.ts` - `packages/editor/src/collaborationHistoryKeys.ts` - `packages/editor/src/collaborationUndo.test.ts` - `packages/editor/src/formatCommands.svelte.test.ts` - `apps/web/e2e/link-webkit-1371.mjs` - `apps/web/package.json` ## UX gaps closed - Undo now removes only the most recent adjacent link; Redo restores only that link. - Shortcut and toolbar creation, selected-word paste, wrapped links, label replacement, link removal, and URL-preview transitions round-trip without duplicate link decorations. - WebKit and Chromium both remained responsive after each action. The run captured 50 screenshots per engine at 1440×900, macOS platform emulation, in light and dark themes. ## UX gap left - A multiword replacement at the end of a link still follows the current Tiptap Link trailing-whitespace behavior. The initial multiword experiment kept only the first word inside the link in both engines. I left that existing behavior unchanged; the regression replay replaces the label with one word and checks its Undo/Redo round-trip. ## Decisions - DESIGN §9 does not define editor history grouping. I chose one link command per Undo/Redo step. ProseMirror `closeHistory` alone did not separate Notes' live Yjs UndoManager, so the command closes both capture windows on either side of the mutation. - The label edit uses Playwright's single text-input transaction so this check measures one replacement action, independent of time-based grouping between individual key events. Selection and link actions use the actual editor UI. - Each link action adds constant-time history boundaries; the existing selected-range mark operation remains the work that scales with the selected text. Renderer responsiveness probe p50/p95/max was 10/84/140 ms in WebKit and 3/14/21 ms in Chromium. These are post-action renderer probes, not end-to-end save latency. ## Screenshot attachments Each ZIP contains the original 1440×900 PNGs for its engine/theme. The three parts for each theme cover all 25 captured states. - WebKit light: [part 1](https://git.kayg.org/attachments/ecde2eea-d685-46bb-a9f3-43c073e8cfd7), [part 2](https://git.kayg.org/attachments/26ab2cc3-ae05-45e5-942c-d822d3ee660a), [part 3](https://git.kayg.org/attachments/0eb5217f-ee60-4ec4-ac0a-5e1d65a3ab82) - WebKit dark: [part 1](https://git.kayg.org/attachments/322cffaa-1836-4063-ab53-f2972e7cbf5d), [part 2](https://git.kayg.org/attachments/152fe5b9-e55c-4503-a332-bb7af32dceed), [part 3](https://git.kayg.org/attachments/830ad69e-7541-4007-9ee6-30300a132b0f) - Chromium light: [part 1](https://git.kayg.org/attachments/b524fbb3-acba-4b06-b81f-fdb2b27cc0ef), [part 2](https://git.kayg.org/attachments/6db4b6b3-8842-43b5-a4b6-0d8dc6ce0cec), [part 3](https://git.kayg.org/attachments/605e3dcd-cf6f-4df0-b56f-32223723c1c0) - Chromium dark: [part 1](https://git.kayg.org/attachments/95dbc4e4-6219-4401-835c-cbac0e64d8e1), [part 2](https://git.kayg.org/attachments/5a93fe36-68f1-4416-a0df-2e7441c624d1), [part 3](https://git.kayg.org/attachments/9d5e6c8b-31ed-4d83-8223-958f52ccb7d1) ## Verification output (verbatim) ```text cargo build -p calternal-server --release Finished `release` profile [optimized] target(s) in 6m 29s cd apps/web && bun run build ✓ built in 3m 44s Wrote site to "build" Compressed 914 static variants; saved 21488159 bytes. cd apps/web && bun run check perf-lint: PASS; 0 violations; 22543 scoped exceptions svelte-check found 0 errors and 4 warnings in 2 files cd apps/web && bun run test Test Files 293 passed (293) Tests 2103 passed (2103) Duration 634.83s (transform 33%, environment 23%, import 20%, tests 18%, setup 5%) cd packages/editor && bun run test -- src/collaborationUndo.test.ts src/formatCommands.svelte.test.ts Test Files 2 passed (2) Tests 34 passed (34) Duration 60.10s (transform 50.11s, setup 930ms, import 69.73s, tests 7.37s, environment 35.28s) production replay RESULT webkit {"consoleErrors":1,"pageErrors":0,"screenshots":50,"probes":27,"maxEvaluateMs":140,"typedProbes":1,"maxTypingMs":50} CONSOLE webkit ["Viewport argument key \"interactive-widget\" not recognized and ignored."] RESULT chromium {"consoleErrors":0,"pageErrors":0,"screenshots":50,"probes":27,"maxEvaluateMs":21,"typedProbes":1,"maxTypingMs":32} PASS #1371 deterministic WebKit and Chromium link actions ``` ## Commits and head - Feature commit: `0fd948901` — `fix(editor): isolate link actions in collaborative history` - Head after merging `origin/dev`: `fc0e8bf7fdc299ac1f7e06838e29b34ea71ee83a`
Author
Owner

Confirmed related data-corruption finding on a clean API-created Note in the production build (Chromium, branch server REV d395b6e25). The Note had a Links heading, a Markdown link plus a bare URL in one paragraph, and an autolink plus wikilink in the next.

Steps: open the Note, click Copy link to Links once, then wait about 1–2 seconds without editing the body.

Expected: only the heading gains its stable ^block-id; both link paragraphs stay unchanged.

Actual: the first API body read 0.5 seconds after the click was intact. By about 1.5–2 seconds after the click, the saved body had merged the next paragraph into the first and duplicated the autolink and wikilink content. The body contained 8 autolink URL references and 4 wikilink references; the source had one of each. The user-visible production screenshot shows the duplicated links.

Severity: major (saved Note content is corrupted). Evidence: before artifacts/bash3-notes/copy-link-before-1440-light.png; after artifacts/bash3-notes/copy-stage-after-pointer-1440.png. This is in the shared editor core, so this bugbash files it only.

Confirmed related data-corruption finding on a clean API-created Note in the production build (Chromium, branch server REV d395b6e25). The Note had a `Links` heading, a Markdown link plus a bare URL in one paragraph, and an autolink plus wikilink in the next. Steps: open the Note, click **Copy link to Links** once, then wait about 1–2 seconds without editing the body. Expected: only the heading gains its stable `^block-id`; both link paragraphs stay unchanged. Actual: the first API body read 0.5 seconds after the click was intact. By about 1.5–2 seconds after the click, the saved body had merged the next paragraph into the first and duplicated the autolink and wikilink content. The body contained 8 autolink URL references and 4 wikilink references; the source had one of each. The user-visible production screenshot shows the duplicated links. Severity: major (saved Note content is corrupted). Evidence: before `artifacts/bash3-notes/copy-link-before-1440-light.png`; after `artifacts/bash3-notes/copy-stage-after-pointer-1440.png`. This is in the shared editor core, so this bugbash files it only.
Author
Owner

Repeated the repro on a second fresh API-created Note and captured the before and after on the same Note ID. The body was intact at 500 ms after Copy link and after the pointer left the heading; by the next idle capture about 1–2 seconds later, the saved body was duplicated (8 autolink URL references and 4 wikilink references, from one each). Same-Note screenshots: artifacts/bash3-notes/copy-link-before-same-1440-light.png and artifacts/bash3-notes/copy-stage-after-pointer-1440.png. The API body and screenshot both show the corruption.

Repeated the repro on a second fresh API-created Note and captured the before and after on the same Note ID. The body was intact at 500 ms after Copy link and after the pointer left the heading; by the next idle capture about 1–2 seconds later, the saved body was duplicated (8 autolink URL references and 4 wikilink references, from one each). Same-Note screenshots: `artifacts/bash3-notes/copy-link-before-same-1440-light.png` and `artifacts/bash3-notes/copy-stage-after-pointer-1440.png`. The API body and screenshot both show the corruption.
kayg referenced this issue from a commit 2026-10-08 10:41:40 +00:00
Author
Owner

Deployed to calternal.cloud in merge round 35a (build cc1af3c12, live 2026-10-09 00:12 IST). Closing.

Deployed to calternal.cloud in merge round 35a (build cc1af3c12, live 2026-10-09 00:12 IST). Closing.
kayg closed this issue 2026-10-08 18:41:33 +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#1371
No description provided.