Selecting a block reloads the Note view #1147

Open
opened 2026-10-05 13:33:27 +00:00 by kayg · 3 comments
Owner

Split from #1132 (owner report, 2026-10-05, production, desktop)

"If I select a block now it reloads." Selecting a block in a Note or Journal entry makes the view reload. #1132 could not reproduce it on a test Instance and its e2e (same URL, same editor instance after block selection) passes, so the trigger is likely data- or state-specific.

Likely suspects to check on a production-shaped Home: block deep-link URL update (#block-… / ?block=) causing a SvelteKit navigation that remounts NoteView; an anchor backfill write (ensureBlockIdAt) that changes the file and triggers a remote-change reload of the same Note; the refused-room fallback (Notes with path: IDs, fixed by #1132's identity backfill) reloading on its own save. Reproduce with a Note that has no calternal-id, a Daily note, and a Note with existing block anchors, in Chromium and WebKit, pointer and keyboard selection. Record navigation events, editor remounts and file writes.

Fix the cause; e2e asserts no navigation, no remount, and no file write from selection alone.

## Split from #1132 (owner report, 2026-10-05, production, desktop) "If I select a block now it reloads." Selecting a block in a Note or Journal entry makes the view reload. #1132 could not reproduce it on a test Instance and its e2e (same URL, same editor instance after block selection) passes, so the trigger is likely data- or state-specific. Likely suspects to check on a production-shaped Home: block deep-link URL update (`#block-…` / `?block=`) causing a SvelteKit navigation that remounts NoteView; an anchor backfill write (ensureBlockIdAt) that changes the file and triggers a remote-change reload of the same Note; the refused-room fallback (Notes with `path:` IDs, fixed by #1132's identity backfill) reloading on its own save. Reproduce with a Note that has no `calternal-id`, a Daily note, and a Note with existing block anchors, in Chromium and WebKit, pointer and keyboard selection. Record navigation events, editor remounts and file writes. Fix the cause; e2e asserts no navigation, no remount, and no file write from selection alone.
Author
Owner

Starting #1147 on job/blockreload-1147, based on #1132 commit 6bf7074e3e53f4e6a943bb273dd4834d2becc4e4 (branch job/editreg-1132). I will trace selection through URL state, editor lifecycle and file writes, then add the reported regression coverage.

Starting #1147 on `job/blockreload-1147`, based on #1132 commit `6bf7074e3e53f4e6a943bb273dd4834d2becc4e4` (branch `job/editreg-1132`). I will trace selection through URL state, editor lifecycle and file writes, then add the reported regression coverage.
Author
Owner

#1147 finding, reproduced on the production SPA and real local server. In Chromium, opening an anchored Markdown Note from Files at /f/<file-id>/edit and choosing “Go to Second section” changed the route to /n/<note-id>#second-section. The document did not reload, but the ProseMirror node was replaced (editorSame: false). No Notes/Files write request occurred. Pointer selection and ArrowDown selection on this same Note preserved the editor, URL, history and caused no write. NoteView.selectHeading builds its URL with noteRoute() instead of the existing source-aware editorRoute(), so a Files editor is replaced by the Notes route. I’m fixing that route selection and will add the regression assertion for Chromium and WebKit.

#1147 finding, reproduced on the production SPA and real local server. In Chromium, opening an anchored Markdown Note from Files at `/f/<file-id>/edit` and choosing “Go to Second section” changed the route to `/n/<note-id>#second-section`. The document did not reload, but the ProseMirror node was replaced (`editorSame: false`). No Notes/Files write request occurred. Pointer selection and ArrowDown selection on this same Note preserved the editor, URL, history and caused no write. `NoteView.selectHeading` builds its URL with `noteRoute()` instead of the existing source-aware `editorRoute()`, so a Files editor is replaced by the Notes route. I’m fixing that route selection and will add the regression assertion for Chromium and WebKit.
Author
Owner

Result

Fixed the Note editor reload when a Contents heading was activated from Files or the legacy /notes/ route. selectHeading used goto to change to the canonical Note route. That route navigation remounted NoteView. It made no document request or write, but discarded the current editor instance.

NoteView now preserves the route that opened the Note and uses SvelteKit shallow pushState to add the stable heading hash. This keeps the current editor mounted and gives the heading its own browser history entry.

The production-server regression covers a Note without calternal-id after startup identity backfill, a Journal-created Daily Note, a Note with existing block anchors, and the legacy /notes/ alias. Chromium and WebKit checks cover pointer, touch, and keyboard block selection. Selection keeps the URL, history, and editor instance, with no Notes or Files writes. Contents activation keeps the current route and editor, adds one history entry, and causes no document fetch or write.

Files

  • apps/web/src/lib/notes/NoteView.svelte — preserve the opening route and shallowly add the heading hash.
  • apps/web/e2e/blockreload-1147.mjs — real-server Chromium and WebKit regression, including touch selection and startup identity backfill.
  • bench/notes-tocrail-636.mjs, docs/perf/notes-tocrail-636.md — Files-origin hot-path profile and measured Round 3 results.
  • apps/web/src/lib/calendar/MonthGrid.svelte.test.ts — add the required optional noteId: null to an existing file fixture after merging origin/dev; no expectation changed.

Screenshot set: 24 macOS-rendered Chromium screenshots, 390/820/1440 px in light and dark themes. The archive is attached to this issue. The production screenshot run completed with:

PASS #1147 block-selection stability in Chromium and WebKit; screenshots: /home/kayg/Developer/calternal-wt/blockreload-1147/artifacts/blockreload-1147

The final touch-enabled matrix also passed in both engines across all four fixtures.

UX gaps closed

Pointer, touch, and keyboard selection now share the no-reload behavior. The Contents heading action also preserves the Files or legacy editor surface while keeping the stable heading link and browser history.

UX gaps left

No interaction gaps remain for this fix. Visual review is for the orchestrator; the full screenshot set is attached above.

Performance

Profiled locally on calternal-dev, since this run did not use the single-tenant perf VM. Host load average was 19.38, 25.63, 23.23 before and 22.61, 25.23, 23.25 after.

Workload p50 p95 CPU / mean RSS / peak RSS
Open six-heading Note (10 samples) 1,812.3 ms 2,354.7 ms 60.50 s / 250.1%; 860,494,154 / 944,492,544 bytes
Reveal heading (20 samples) 232.9 ms 442.7 ms included above
Open 1,000-heading Note 3,271.6 ms — 18.58 s / 192.2%; 1,050,376,875 / 1,163,595,776 bytes
100 ArrowDown presses 13.8 ms 30.2 ms included above
100 heading-link activations 50.0 ms 126.7 ms included above

docs/perf/baseline.json has no Note heading-rail metric. Its nearest Notes Explorer entry is 1,257 ms p50 / 1,337 ms p95; the workloads differ, so this is context and not a direct comparison. The browser CPU percentage sums Chromium processes and can exceed 100%. Full numbers are in docs/perf/notes-tocrail-636.md; raw output is in ignored artifacts/blockreload-1147/profile.json.

Gates and verification

  • cargo fmt --check — exit 0; stdout was empty.
  • cargo build -p calternal-server — passed to run the merged production-shaped server:
Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 08s
  • bun run build — passed:
✓ built in 1m 23s
Wrote site to "build"
✔ done
Compressed 885 static variants; saved 20908641 bytes.
  • bun run svelte-check --tsconfig ./tsconfig.json — passed with existing warnings:
svelte-check found 0 errors and 4 warnings in 3 files
  • Focused Vitest — passed:
Not implemented: Window's scrollTo() method

 Test Files  2 passed (2)
      Tests  5 passed (5)
  • bun run check stopped at the merged origin/dev perf-lint exception ratchet. I compared HEAD^2:contracts/perf/ratchet.json and HEAD^2:contracts/perf/exceptions.json; these excess counts are in the merged base, not this issue's changes. Output:
perf-lint: INVALID: exception ratchet: contract.blaze: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.blocked-network: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; contract.dom-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.model-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.row-identity: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.snapshot: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.profile: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.readiness: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; io.unresolved-call: 16191 exceptions exceed the ratchet ceiling 16190; fix the new violation instead; render.blaze-adapter: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_byte_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_row_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; total: ledger has 21977 exceptions; ratchet total is 21964

No Rust source changed for this issue, so no crate clippy or test gates were run. The full web test suite remains for the merge round; this job ran the focused Note tests and issue-specific browser matrix per verification policy.

Decisions

  • Keep heading selection on the surface that opened the Note (/f/<id>/edit or /notes/<id>) and update only its stable heading hash through shallow history. This addresses the reproduced SvelteKit remount while retaining a deep link.
  • Add noteId: null only to the merged Calendar test fixture to satisfy its updated type.

Commits

Head: b0c9ea2c41b85917a7b1b79f18309982762bf448

The branch contains separate commits for the route fix, fixture type update, performance profile, profile results, and touch coverage. No push, deploy, merge, or issue close was performed.

## Result Fixed the Note editor reload when a Contents heading was activated from Files or the legacy `/notes/` route. `selectHeading` used `goto` to change to the canonical Note route. That route navigation remounted `NoteView`. It made no document request or write, but discarded the current editor instance. `NoteView` now preserves the route that opened the Note and uses SvelteKit shallow `pushState` to add the stable heading hash. This keeps the current editor mounted and gives the heading its own browser history entry. The production-server regression covers a Note without `calternal-id` after startup identity backfill, a Journal-created Daily Note, a Note with existing block anchors, and the legacy `/notes/` alias. Chromium and WebKit checks cover pointer, touch, and keyboard block selection. Selection keeps the URL, history, and editor instance, with no Notes or Files writes. Contents activation keeps the current route and editor, adds one history entry, and causes no document fetch or write. ## Files - `apps/web/src/lib/notes/NoteView.svelte` — preserve the opening route and shallowly add the heading hash. - `apps/web/e2e/blockreload-1147.mjs` — real-server Chromium and WebKit regression, including touch selection and startup identity backfill. - `bench/notes-tocrail-636.mjs`, `docs/perf/notes-tocrail-636.md` — Files-origin hot-path profile and measured Round 3 results. - `apps/web/src/lib/calendar/MonthGrid.svelte.test.ts` — add the required optional `noteId: null` to an existing file fixture after merging `origin/dev`; no expectation changed. Screenshot set: [24 macOS-rendered Chromium screenshots, 390/820/1440 px in light and dark themes](https://git.kayg.org/attachments/dfd1eb9a-3a47-4c28-95ee-275006b7cbb6). The archive is attached to this issue. The production screenshot run completed with: ```text PASS #1147 block-selection stability in Chromium and WebKit; screenshots: /home/kayg/Developer/calternal-wt/blockreload-1147/artifacts/blockreload-1147 ``` The final touch-enabled matrix also passed in both engines across all four fixtures. ## UX gaps closed Pointer, touch, and keyboard selection now share the no-reload behavior. The Contents heading action also preserves the Files or legacy editor surface while keeping the stable heading link and browser history. ## UX gaps left No interaction gaps remain for this fix. Visual review is for the orchestrator; the full screenshot set is attached above. ## Performance Profiled locally on `calternal-dev`, since this run did not use the single-tenant perf VM. Host load average was `19.38, 25.63, 23.23` before and `22.61, 25.23, 23.25` after. | Workload | p50 | p95 | CPU / mean RSS / peak RSS | | --- | ---: | ---: | --- | | Open six-heading Note (10 samples) | 1,812.3 ms | 2,354.7 ms | 60.50 s / 250.1%; 860,494,154 / 944,492,544 bytes | | Reveal heading (20 samples) | 232.9 ms | 442.7 ms | included above | | Open 1,000-heading Note | 3,271.6 ms | — | 18.58 s / 192.2%; 1,050,376,875 / 1,163,595,776 bytes | | 100 ArrowDown presses | 13.8 ms | 30.2 ms | included above | | 100 heading-link activations | 50.0 ms | 126.7 ms | included above | `docs/perf/baseline.json` has no Note heading-rail metric. Its nearest Notes Explorer entry is 1,257 ms p50 / 1,337 ms p95; the workloads differ, so this is context and not a direct comparison. The browser CPU percentage sums Chromium processes and can exceed 100%. Full numbers are in `docs/perf/notes-tocrail-636.md`; raw output is in ignored `artifacts/blockreload-1147/profile.json`. ## Gates and verification - `cargo fmt --check` — exit 0; stdout was empty. - `cargo build -p calternal-server` — passed to run the merged production-shaped server: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 3m 08s ``` - `bun run build` — passed: ```text ✓ built in 1m 23s Wrote site to "build" ✔ done Compressed 885 static variants; saved 20908641 bytes. ``` - `bun run svelte-check --tsconfig ./tsconfig.json` — passed with existing warnings: ```text svelte-check found 0 errors and 4 warnings in 3 files ``` - Focused Vitest — passed: ```text Not implemented: Window's scrollTo() method Test Files 2 passed (2) Tests 5 passed (5) ``` - `bun run check` stopped at the merged `origin/dev` perf-lint exception ratchet. I compared `HEAD^2:contracts/perf/ratchet.json` and `HEAD^2:contracts/perf/exceptions.json`; these excess counts are in the merged base, not this issue's changes. Output: ```text perf-lint: INVALID: exception ratchet: contract.blaze: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.blocked-network: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; contract.dom-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.model-bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.row-identity: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; contract.snapshot: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.profile: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; coverage.readiness: 356 exceptions exceed the ratchet ceiling 355; fix the new violation instead; io.unresolved-call: 16191 exceptions exceed the ratchet ceiling 16190; fix the new violation instead; render.blaze-adapter: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.bound: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_byte_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; render.model_row_cap: 290 exceptions exceed the ratchet ceiling 289; fix the new violation instead; total: ledger has 21977 exceptions; ratchet total is 21964 ``` No Rust source changed for this issue, so no crate clippy or test gates were run. The full web test suite remains for the merge round; this job ran the focused Note tests and issue-specific browser matrix per verification policy. ## Decisions - Keep heading selection on the surface that opened the Note (`/f/<id>/edit` or `/notes/<id>`) and update only its stable heading hash through shallow history. This addresses the reproduced SvelteKit remount while retaining a deep link. - Add `noteId: null` only to the merged Calendar test fixture to satisfy its updated type. ## Commits Head: `b0c9ea2c41b85917a7b1b79f18309982762bf448` The branch contains separate commits for the route fix, fixture type update, performance profile, profile results, and touch coverage. No push, deploy, merge, or issue close was performed.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
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#1147
No description provided.