WIP: feat(whiteboard): file and sync foundation (slice 1) #1015

Draft
kayg wants to merge 35 commits from feat/whiteboard-s1 into dev
Owner

Plan

Implement Whiteboard slice 1 as a read-only-on-open .excalidraw.md format plug-in and a structured room contract on Notes' existing Yrs 0.28 sync path. Plain JSON stays byte-preserving for unknown fields and sections; compressed legacy files stay byte-identical until a real edit, which writes uncompressed JSON and retains decoded unknown data. Element IDs are stable eight-character Obsidian-compatible IDs. There is no board UI and no feature flag in this slice.

Limits and complexity

  • Board file: 16 MiB; JSON depth: 64.
  • Elements: 10,000 active and 20,000 including tombstones; one sync update: 256 KiB.
  • Durable updates: 30/sec/client sustained, burst 120 over 2 sec; presence: 20/sec/client; room: 10 collaborators.
  • Parsing, validation, and serialization are O(bytes + elements); stable-ID normalization is O(elements) average; ordered scene materialization is O(elements log elements). Reindexing is O(elements) and occurs only when fractional order keys run out. The bounds cap each operation's input.
  • Durable scene data is a Y.Map named elements, keyed by stable element ID, using the existing Notes route and room writer. Local view state remains local.

Failure table

Failure Effect Detection Recovery Test
Unknown JSON field or Markdown section A rewrite could damage content owned by another tool Parser tracks unowned byte ranges Keep original bytes; refuse an unsafe rewrite Plain and compressed unknown-data fixtures, no-op and changed saves
Oversize or malformed board/update Room state must remain valid Byte, depth, count, schema, SVG, and URL checks before apply Reject before mutation; keep last valid file rejects_large_or_malformed_updates_before_apply
Same element changes from one base One edit could overwrite another Compare ID, version, nonce, base, and operation IDs Record complete candidates before canonical reconciliation Both arrival orders and merged batch tests
Save interrupted or reply dropped A client may retain an unconfirmed local edit Save history, file version, and operation identity Retry idempotently; acknowledge only after durable write Save boundary and offline retry tests
Grant revoked while connected/offline Old session could keep reading or writing Recheck grant on updates and outgoing frames Fence session; keep unsent work in recovery revoked_updates_and_frames_are_refused
Temporary rate limit A valid operation could be lost Retry interval and operation ID Keep the same operation in the existing outbox until accepted rate_limited_operation_stays_in_outbox
Legacy inline attachment migration Safe item identity and retry depend on Files identity Files identity API is not on dev yet; see open #1010 Do not create a parallel identity layer; keep this PR draft until the shared API is available legacy_attachment_migration_is_recoverable_at_each_boundary remains pending

First-PR gates

The slice includes the no-write open tests, plain/compressed round trips, stable-ID and text/link projections, admission limits, Y.Map convergence and reconciliation, authorized save/debounce, revoke fencing, offline retry, rate-limit retry, board/item permission isolation, and the 2,000-element, 300-reference, and 10-client fixtures. The approved bundle gate requires zero first-paint Whiteboard JS/CSS and measured lazy editor budgets with initial: false; Notes save/sync requires ABAB ×5 at one perf epoch with a dev-vs-dev control. Gate results and measured tables will be posted in PR comments.

The legacy attachment migration test is the one dependency gap: current dev has no stable Files item identity contract. This PR is WIP while the shared Files identity API and required Notes perf comparison are pending. It is not ready for merge.

## Plan Implement Whiteboard slice 1 as a read-only-on-open `.excalidraw.md` format plug-in and a structured room contract on Notes' existing Yrs 0.28 sync path. Plain JSON stays byte-preserving for unknown fields and sections; compressed legacy files stay byte-identical until a real edit, which writes uncompressed JSON and retains decoded unknown data. Element IDs are stable eight-character Obsidian-compatible IDs. There is no board UI and no feature flag in this slice. ### Limits and complexity - Board file: 16 MiB; JSON depth: 64. - Elements: 10,000 active and 20,000 including tombstones; one sync update: 256 KiB. - Durable updates: 30/sec/client sustained, burst 120 over 2 sec; presence: 20/sec/client; room: 10 collaborators. - Parsing, validation, and serialization are O(bytes + elements); stable-ID normalization is O(elements) average; ordered scene materialization is O(elements log elements). Reindexing is O(elements) and occurs only when fractional order keys run out. The bounds cap each operation's input. - Durable scene data is a Y.Map named `elements`, keyed by stable element ID, using the existing Notes route and room writer. Local view state remains local. ### Failure table | Failure | Effect | Detection | Recovery | Test | |---|---|---|---|---| | Unknown JSON field or Markdown section | A rewrite could damage content owned by another tool | Parser tracks unowned byte ranges | Keep original bytes; refuse an unsafe rewrite | Plain and compressed unknown-data fixtures, no-op and changed saves | | Oversize or malformed board/update | Room state must remain valid | Byte, depth, count, schema, SVG, and URL checks before apply | Reject before mutation; keep last valid file | `rejects_large_or_malformed_updates_before_apply` | | Same element changes from one base | One edit could overwrite another | Compare ID, version, nonce, base, and operation IDs | Record complete candidates before canonical reconciliation | Both arrival orders and merged batch tests | | Save interrupted or reply dropped | A client may retain an unconfirmed local edit | Save history, file version, and operation identity | Retry idempotently; acknowledge only after durable write | Save boundary and offline retry tests | | Grant revoked while connected/offline | Old session could keep reading or writing | Recheck grant on updates and outgoing frames | Fence session; keep unsent work in recovery | `revoked_updates_and_frames_are_refused` | | Temporary rate limit | A valid operation could be lost | Retry interval and operation ID | Keep the same operation in the existing outbox until accepted | `rate_limited_operation_stays_in_outbox` | | Legacy inline attachment migration | Safe item identity and retry depend on Files identity | Files identity API is not on `dev` yet; see open #1010 | Do not create a parallel identity layer; keep this PR draft until the shared API is available | `legacy_attachment_migration_is_recoverable_at_each_boundary` remains pending | ### First-PR gates The slice includes the no-write open tests, plain/compressed round trips, stable-ID and text/link projections, admission limits, Y.Map convergence and reconciliation, authorized save/debounce, revoke fencing, offline retry, rate-limit retry, board/item permission isolation, and the 2,000-element, 300-reference, and 10-client fixtures. The approved bundle gate requires zero first-paint Whiteboard JS/CSS and measured lazy editor budgets with `initial: false`; Notes save/sync requires ABAB ×5 at one perf epoch with a dev-vs-dev control. Gate results and measured tables will be posted in PR comments. The legacy attachment migration test is the one dependency gap: current `dev` has no stable Files item identity contract. This PR is WIP while the shared Files identity API and required Notes perf comparison are pending. It is not ready for merge.
kayg added 35 commits 2026-11-08 03:40:16 +00:00
perf(web): keep Whiteboard outside app bundle total
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
95966eb5e7
kayg changed title from feat(whiteboard): file and sync foundation (slice 1) to WIP: feat(whiteboard): file and sync foundation (slice 1) 2026-11-08 03:45:38 +00:00
Author
Owner

Slice 1 evidence: bundle, package research, and focused gates

Bundle spike

Measured on dev-01 from the exact PR head with the existing bundle harness:

nix develop -c bash -c '~/bin/slot bash perf/web.sh --bundle-only --gate'

All sizes below are KiB Brotli.

Scope JavaScript CSS Result
App first paint 225.368 17.955 Existing app budgets pass
Whiteboard before first paint 0 0 Pass; no Whiteboard app-manifest entries
WhiteboardEditor lazy entry 1,085.720 — Measured entry chunk
Full lazy Whiteboard closure 1,915.172 18.010 97 JavaScript chunks

The budget file has 97 measured Whiteboard JavaScript allowances and one CSS allowance, each initial: false. The existing web.bundle.total_js_brotli allowance remains 488.3 KiB; there is no guessed Whiteboard total.

Brave search for “@excalidraw/excalidraw tree shaking package exports” surfaced the package documentation describing an ESM bundle and tree-shakable dependencies. Current npm metadata reports @excalidraw/excalidraw 0.18.1, MIT, root runtime exports plus ./index.css; wildcard exports are types only, and the package does not declare sideEffects. The official integration guide uses the root package import and supports lazy loading; it documents no smaller runtime subpath. This slice therefore isolates the root import behind the lazy editor boundary and measures the actual bundle.

Sources: npm latest metadata, Excalidraw integration guide, Brave search.

Focused gates

Gate Command Result
Rust format cargo fmt --all -- --check Pass
Notes cargo test -p calternal-notes --locked -j4 601 passed, 6 ignored
Web lint bun run lint Pass
Web check bun run check 0 errors, 16 existing warnings
Web unit tests bun run test 166 passed
Legacy attachment recovery legacy_attachment_migration_is_recoverable_at_each_boundary Pending: current dev has no stable Files item identity API; see #1010. No parallel identity layer was added.
Notes hot-path ABAB ×5 calternal-perf on perf-01 with both perf locks Pending; unrelated Mail measurement and a lock holder are still active. This comment will be updated with the full dev/branch table after the run.

The upstream Obsidian fixture provenance and AGPL-3.0 license, matching this repository's root license, are recorded in crates/notes/tests/fixtures/whiteboard/README.md.

## Slice 1 evidence: bundle, package research, and focused gates ### Bundle spike Measured on dev-01 from the exact PR head with the existing bundle harness: `nix develop -c bash -c '~/bin/slot bash perf/web.sh --bundle-only --gate'` All sizes below are KiB Brotli. | Scope | JavaScript | CSS | Result | |---|---:|---:|---| | App first paint | 225.368 | 17.955 | Existing app budgets pass | | Whiteboard before first paint | 0 | 0 | Pass; no Whiteboard app-manifest entries | | `WhiteboardEditor` lazy entry | 1,085.720 | — | Measured entry chunk | | Full lazy Whiteboard closure | 1,915.172 | 18.010 | 97 JavaScript chunks | The budget file has 97 measured Whiteboard JavaScript allowances and one CSS allowance, each `initial: false`. The existing `web.bundle.total_js_brotli` allowance remains 488.3 KiB; there is no guessed Whiteboard total. ### Excalidraw package search Brave search for “@excalidraw/excalidraw tree shaking package exports” surfaced the package documentation describing an ESM bundle and tree-shakable dependencies. Current npm metadata reports `@excalidraw/excalidraw` 0.18.1, MIT, root runtime exports plus `./index.css`; wildcard exports are types only, and the package does not declare `sideEffects`. The official integration guide uses the root package import and supports lazy loading; it documents no smaller runtime subpath. This slice therefore isolates the root import behind the lazy editor boundary and measures the actual bundle. Sources: [npm latest metadata](https://registry.npmjs.org/%40excalidraw%2Fexcalidraw/latest), [Excalidraw integration guide](https://plus.excalidraw.com/docs/sdk/excalidraw/integration), [Brave search](https://search.brave.com/search?q=%40excalidraw%2Fexcalidraw+tree+shaking+package+exports&source=web). ### Focused gates | Gate | Command | Result | |---|---|---| | Rust format | `cargo fmt --all -- --check` | Pass | | Notes | `cargo test -p calternal-notes --locked -j4` | 601 passed, 6 ignored | | Web lint | `bun run lint` | Pass | | Web check | `bun run check` | 0 errors, 16 existing warnings | | Web unit tests | `bun run test` | 166 passed | | Legacy attachment recovery | `legacy_attachment_migration_is_recoverable_at_each_boundary` | Pending: current `dev` has no stable Files item identity API; see #1010. No parallel identity layer was added. | | Notes hot-path ABAB ×5 | `calternal-perf` on perf-01 with both perf locks | Pending; unrelated Mail measurement and a lock holder are still active. This comment will be updated with the full dev/branch table after the run. | The upstream Obsidian fixture provenance and AGPL-3.0 license, matching this repository's root license, are recorded in `crates/notes/tests/fixtures/whiteboard/README.md`.
Author
Owner

Bundle follow-up: module attribution and slice 2 split

Module rows below are Brotli Q11 source-map attribution from the baseline build. Aggregate sizes use the existing bundle harness on dev-01.

Rank Module KiB Reachability
1 Excalidraw chunk-EIO257PC.js 589.600 Editor entry
2 @mermaid-js/parser core 110.530 On demand
3 Cytoscape 3.34.4 109.907 On demand
4 Excalidraw chunk-K2UTITRG.js 108.785 Editor entry
5 Excalidraw index.js 98.658 Editor entry
6 KaTeX 0.16.47 56.385 On demand
7 Mermaid swimlanes 31.695 On demand
8 Mermaid sequence diagram 23.778 On demand
9 Mermaid chunk-4HAMMTFA 20.941 On demand
10 Mermaid chunk-DU6HZSFF 14.729 On demand
11 Mermaid chunk-SHT3W25Y 14.714 On demand
12 image-blob-reduce 3.0.1 14.032 On demand
13 Mermaid C4 diagram 14.031 On demand
14 layout-base 2.0.1 13.580 On demand
15 Mermaid chunk-LNGE3PJU 13.359 On demand

Split experiment

I reran perf/web.sh --bundle-only --gate with a temporary measurement-config change that lets Excalidraw's internal chunks split instead of forcing the whole package into WhiteboardEditor. No product code or budget caps changed.

Measure Current grouping Split experiment Change
App first-paint JS / CSS 225.368 / 17.955 225.368 / 17.955 0
Whiteboard first-paint JS / CSS 0 / 0 0 / 0 0
WhiteboardEditor JS entry 1,085.720 263.201 -822.519
Full lazy JS closure / JS chunks 1,915.172 / 97 2,036.322 / 156 +121.150 / +59
Lazy CSS 18.010 18.010 0

The split route's static JS path is 263.729 KiB including its 0.528 KiB runtime import. The full closure includes every reachable dynamic chunk; it is not the set fetched on editor open. All 55 locale-related chunks (54 language packs, including English, plus percentages) are dynamic: 353.200 KiB total, with English at 0.287 KiB. Mermaid, file-access adapters, and the font-subsetting worker are also dynamic imports. The subset worker/shared chunks are 0.226 / 0.136 KiB.

The package has two resolved roughjs versions (4.6.4 for Excalidraw and 4.6.6 under Mermaid) and two layout-base versions (1.0.2 and 2.0.1). ELK is absent from the lockfile. D3 and Mermaid dependencies are on-demand, not in the editor entry.

Package and font notes for slice 2

The current npm release is @excalidraw/excalidraw@0.18.1 (published 2026-04-20). Its exports expose the root runtime entry and ./index.css; wildcard exports are types only, and there is no sideEffects declaration, so there is no supported smaller runtime subpath for consumer imports. The lock resolves @excalidraw/mermaid-to-excalidraw@2.2.2.

Upstream has since merged lazy Mermaid loading (#12274) and per-diagram Mermaid chunks (#12481), which uses mermaid-to-excalidraw 3.1.0. Those changes post-date the published 0.18.1 package. The integration guide uses the root package import.

Excalidraw's font manager checks the scene's used families and loads only missing font faces. The package contains 234 WOFF2 files (12,800.703 KiB Brotli total), but the split output emits only the four Assistant UI faces (79.199 KiB Brotli); these files are separate assets, outside the JS/CSS closure. The seven existing Google Sans Flex / Maple Mono assets also appear in the normal app output (482.207 KiB Brotli), so they are shared app fonts. Slice 2 should serve the package's selected font files from a same-origin path and request only the family/style in use.

The existing measured Whiteboard budget lines remain unchanged and initial: false; no aggregate Whiteboard total was guessed or added. The split experiment is measurement evidence, not a new budget proposal.

Sources: npm 0.18.1 metadata, integration guide.

## Bundle follow-up: module attribution and slice 2 split Module rows below are Brotli Q11 source-map attribution from the baseline build. Aggregate sizes use the existing bundle harness on dev-01. | Rank | Module | KiB | Reachability | |---:|---|---:|---| | 1 | Excalidraw `chunk-EIO257PC.js` | 589.600 | Editor entry | | 2 | `@mermaid-js/parser` core | 110.530 | On demand | | 3 | Cytoscape 3.34.4 | 109.907 | On demand | | 4 | Excalidraw `chunk-K2UTITRG.js` | 108.785 | Editor entry | | 5 | Excalidraw `index.js` | 98.658 | Editor entry | | 6 | KaTeX 0.16.47 | 56.385 | On demand | | 7 | Mermaid swimlanes | 31.695 | On demand | | 8 | Mermaid sequence diagram | 23.778 | On demand | | 9 | Mermaid `chunk-4HAMMTFA` | 20.941 | On demand | | 10 | Mermaid `chunk-DU6HZSFF` | 14.729 | On demand | | 11 | Mermaid `chunk-SHT3W25Y` | 14.714 | On demand | | 12 | `image-blob-reduce` 3.0.1 | 14.032 | On demand | | 13 | Mermaid C4 diagram | 14.031 | On demand | | 14 | `layout-base` 2.0.1 | 13.580 | On demand | | 15 | Mermaid `chunk-LNGE3PJU` | 13.359 | On demand | ### Split experiment I reran `perf/web.sh --bundle-only --gate` with a temporary measurement-config change that lets Excalidraw's internal chunks split instead of forcing the whole package into `WhiteboardEditor`. No product code or budget caps changed. | Measure | Current grouping | Split experiment | Change | |---|---:|---:|---:| | App first-paint JS / CSS | 225.368 / 17.955 | 225.368 / 17.955 | 0 | | Whiteboard first-paint JS / CSS | 0 / 0 | 0 / 0 | 0 | | `WhiteboardEditor` JS entry | 1,085.720 | 263.201 | -822.519 | | Full lazy JS closure / JS chunks | 1,915.172 / 97 | 2,036.322 / 156 | +121.150 / +59 | | Lazy CSS | 18.010 | 18.010 | 0 | The split route's static JS path is 263.729 KiB including its 0.528 KiB runtime import. The full closure includes every reachable dynamic chunk; it is not the set fetched on editor open. All 55 locale-related chunks (54 language packs, including English, plus percentages) are dynamic: 353.200 KiB total, with English at 0.287 KiB. Mermaid, file-access adapters, and the font-subsetting worker are also dynamic imports. The subset worker/shared chunks are 0.226 / 0.136 KiB. The package has two resolved `roughjs` versions (4.6.4 for Excalidraw and 4.6.6 under Mermaid) and two `layout-base` versions (1.0.2 and 2.0.1). ELK is absent from the lockfile. D3 and Mermaid dependencies are on-demand, not in the editor entry. ### Package and font notes for slice 2 The current npm release is `@excalidraw/excalidraw@0.18.1` (published 2026-04-20). Its exports expose the root runtime entry and `./index.css`; wildcard exports are types only, and there is no `sideEffects` declaration, so there is no supported smaller runtime subpath for consumer imports. The lock resolves `@excalidraw/mermaid-to-excalidraw@2.2.2`. Upstream has since merged [lazy Mermaid loading (#12274)](https://github.com/excalidraw/excalidraw/pull/12274) and [per-diagram Mermaid chunks (#12481)](https://github.com/excalidraw/excalidraw/pull/12481), which uses mermaid-to-excalidraw 3.1.0. Those changes post-date the published 0.18.1 package. The [integration guide](https://plus.excalidraw.com/docs/sdk/excalidraw/integration) uses the root package import. Excalidraw's font manager checks the scene's used families and loads only missing font faces. The package contains 234 WOFF2 files (12,800.703 KiB Brotli total), but the split output emits only the four Assistant UI faces (79.199 KiB Brotli); these files are separate assets, outside the JS/CSS closure. The seven existing Google Sans Flex / Maple Mono assets also appear in the normal app output (482.207 KiB Brotli), so they are shared app fonts. Slice 2 should serve the package's selected font files from a same-origin path and request only the family/style in use. The existing measured Whiteboard budget lines remain unchanged and `initial: false`; no aggregate Whiteboard total was guessed or added. The split experiment is measurement evidence, not a new budget proposal. Sources: [npm 0.18.1 metadata](https://registry.npmjs.org/%40excalidraw%2Fexcalidraw/latest), [integration guide](https://plus.excalidraw.com/docs/sdk/excalidraw/integration).
Author
Owner

Latest-head bundle follow-up for slice 2

Measured on dev-01 at 40d588eee with nix develop -c bash -c '~/bin/slot bash perf/web.sh --bundle-only --gate'. Sizes are KiB Brotli.

Measure Earlier grouped build Split build at latest head
App first paint JS / CSS 225.368 / 17.955 225.279 / 17.955
Whiteboard first paint JS / CSS 0 / 0 0 / 0
Editor entry / open static path JS 1,085.720 263.729
Full dynamic Whiteboard JS closure / chunks 1,915.172 / 97 2,036.322 / 156
Whiteboard editor CSS — 18.010

The editor open path is 821.991 KiB smaller (75.7%). The full optional closure grows 121.150 KiB because the split emits more independently compressed chunks; it counts every dynamic feature, not a normal English board open. The app-only observed total is 473.060 KiB against the existing 488.3 KiB allowance. The budget gate passes. Each Whiteboard chunk budget is measured and initial: false; there is no Whiteboard aggregate allowance.

For slice 2, keep Mermaid behind its on-demand import (or drop the integration if it is not needed): the parser core is 110.530 KiB and Cytoscape is 109.907 KiB, both on demand; D3 dependencies are also on demand and ELK is absent from the lockfile. Locale output is 55 dynamic chunks / 353.200 KiB, with English at 0.287 KiB, so only English is needed on a default-language open and a selected locale can load when requested. The Excalidraw font manager loads missing families when the scene uses them. The package contains 234 WOFF2 files (12,800.703 KiB total); this build emits four Assistant UI faces (79.199 KiB) as separate assets, not JS/CSS. The font subset worker and its shared chunk are 0.226 / 0.136 KiB and dynamic.

The top-15 Brotli module table and duplicate roughjs / layout-base notes remain in the earlier bundle comment. The npm registry still reports 0.18.1 as latest, MIT, with only the root runtime and CSS runtime exports (wildcards are types; no sideEffects declaration). Upstream Mermaid lazy-loading changes #12274 and #12481 post-date 0.18.1. See the earlier comment for the web-search and integration-guide sources.

## Latest-head bundle follow-up for slice 2 Measured on dev-01 at `40d588eee` with `nix develop -c bash -c '~/bin/slot bash perf/web.sh --bundle-only --gate'`. Sizes are KiB Brotli. | Measure | Earlier grouped build | Split build at latest head | |---|---:|---:| | App first paint JS / CSS | 225.368 / 17.955 | 225.279 / 17.955 | | Whiteboard first paint JS / CSS | 0 / 0 | 0 / 0 | | Editor entry / open static path JS | 1,085.720 | 263.729 | | Full dynamic Whiteboard JS closure / chunks | 1,915.172 / 97 | 2,036.322 / 156 | | Whiteboard editor CSS | — | 18.010 | The editor open path is 821.991 KiB smaller (75.7%). The full optional closure grows 121.150 KiB because the split emits more independently compressed chunks; it counts every dynamic feature, not a normal English board open. The app-only observed total is 473.060 KiB against the existing 488.3 KiB allowance. The budget gate passes. Each Whiteboard chunk budget is measured and `initial: false`; there is no Whiteboard aggregate allowance. For slice 2, keep Mermaid behind its on-demand import (or drop the integration if it is not needed): the parser core is 110.530 KiB and Cytoscape is 109.907 KiB, both on demand; D3 dependencies are also on demand and ELK is absent from the lockfile. Locale output is 55 dynamic chunks / 353.200 KiB, with English at 0.287 KiB, so only English is needed on a default-language open and a selected locale can load when requested. The Excalidraw font manager loads missing families when the scene uses them. The package contains 234 WOFF2 files (12,800.703 KiB total); this build emits four Assistant UI faces (79.199 KiB) as separate assets, not JS/CSS. The font subset worker and its shared chunk are 0.226 / 0.136 KiB and dynamic. The top-15 Brotli module table and duplicate `roughjs` / `layout-base` notes remain in [the earlier bundle comment](https://git.kayg.org/kayg/calternal/pulls/1015#issuecomment-86444). The npm registry still reports 0.18.1 as latest, MIT, with only the root runtime and CSS runtime exports (wildcards are types; no `sideEffects` declaration). Upstream Mermaid lazy-loading changes [#12274](https://github.com/excalidraw/excalidraw/pull/12274) and [#12481](https://github.com/excalidraw/excalidraw/pull/12481) post-date 0.18.1. See the earlier comment for the web-search and integration-guide sources.
Author
Owner

Slice 2's measured default-language open path is 264.016 KiB JavaScript (263.729 KiB static editor path plus the disjoint 0.287 KiB English locale chunk) and 18.010 KiB CSS. Mermaid, other locale packs, and scene-font files remain on demand; this is a measured path, not an aggregate budget.

Slice 2's measured default-language open path is 264.016 KiB JavaScript (263.729 KiB static editor path plus the disjoint 0.287 KiB English locale chunk) and 18.010 KiB CSS. Mermaid, other locale packs, and scene-font files remain on demand; this is a measured path, not an aggregate budget.
Author
Owner

The 0.18.1 export map also has no separate laser or collaboration runtime subpath. The top-15 source-map table attributes the Excalidraw modules, but cannot split those features out as consumer-selectable entries; dropping them would require an upstream build option or excluding the feature from v1.

The 0.18.1 export map also has no separate laser or collaboration runtime subpath. The top-15 source-map table attributes the Excalidraw modules, but cannot split those features out as consumer-selectable entries; dropping them would require an upstream build option or excluding the feature from v1.
Author
Owner

Notes hot-path ABAB ×5

Measured on perf-02 on 2026-11-08, 16:52–17:09 IST. The before/after boot ID stayed 94fef04b-c6e4-4d3d-966d-9135d6f2dda1, with CPUs 0-3 online and pinned. The run used a fresh 10k Home per report, seed 1, and the same calternal-perf harness for dev and PR binaries.

Binary hashes: dev calternal 7c66ecd5b294523fbc56855f37d387ee8eb915709fbfe4b142c343a08e20f2c2; PR calternal daac9ecd8bdbdcb20aca4521b91b74a3eab092e6dfe14c143ebe3b20d31a794d; harness calternal-perf e2deb2e1bc2495c96a8d957a733e7e70cc0852867300cf4a279d0f322fb5a60e.

Sequence: five dev controls, five A-B-A-B rounds (A=dev, B=PR), then five dev controls. All 30 reports exited successfully and contained all three metrics. Each report had 200 sync.round_trip and sync.save_ack samples, and 30 sync.durable_write samples.

Values are medians of each report’s p50/p95. Sizes are milliseconds. B−A and dev post−pre compare p95.

Path Dev pre p50 / p95 A arms p50 / p95 PR B arms p50 / p95 Dev post p50 / p95 B−A p95 Dev post−pre p95
sync.round_trip 0.209 / 0.342 0.213 / 0.339 0.205 / 0.347 0.207 / 0.338 +0.008 (+2.3%) -0.005 (-1.4%)
sync.save_ack 4.378 / 4.895 5.413 / 7.644 5.325 / 7.277 4.418 / 5.034 -0.368 (-4.8%) +0.139 (+2.8%)
sync.durable_write 15.356 / 20.181 20.790 / 28.655 19.925 / 24.211 15.394 / 18.268 -4.444 (-15.5%) -1.913 (-9.5%)

The dev pre/post controls remained close on round-trip and save acknowledgement. The PR medians show lower p50s on all three paths, lower p95s on save acknowledgement and durable write, and a 0.008 ms round-trip p95 increase (within the run-to-run spread). No material Notes hot-path regression appeared in this ABAB run.

## Notes hot-path ABAB ×5 Measured on perf-02 on 2026-11-08, 16:52–17:09 IST. The before/after boot ID stayed `94fef04b-c6e4-4d3d-966d-9135d6f2dda1`, with CPUs `0-3` online and pinned. The run used a fresh 10k Home per report, seed 1, and the same `calternal-perf` harness for dev and PR binaries. Binary hashes: dev `calternal` `7c66ecd5b294523fbc56855f37d387ee8eb915709fbfe4b142c343a08e20f2c2`; PR `calternal` `daac9ecd8bdbdcb20aca4521b91b74a3eab092e6dfe14c143ebe3b20d31a794d`; harness `calternal-perf` `e2deb2e1bc2495c96a8d957a733e7e70cc0852867300cf4a279d0f322fb5a60e`. Sequence: five dev controls, five `A-B-A-B` rounds (`A`=dev, `B`=PR), then five dev controls. All 30 reports exited successfully and contained all three metrics. Each report had 200 `sync.round_trip` and `sync.save_ack` samples, and 30 `sync.durable_write` samples. Values are medians of each report’s p50/p95. Sizes are milliseconds. `B−A` and `dev post−pre` compare p95. | Path | Dev pre p50 / p95 | A arms p50 / p95 | PR B arms p50 / p95 | Dev post p50 / p95 | B−A p95 | Dev post−pre p95 | |---|---:|---:|---:|---:|---:|---:| | `sync.round_trip` | 0.209 / 0.342 | 0.213 / 0.339 | 0.205 / 0.347 | 0.207 / 0.338 | +0.008 (+2.3%) | -0.005 (-1.4%) | | `sync.save_ack` | 4.378 / 4.895 | 5.413 / 7.644 | 5.325 / 7.277 | 4.418 / 5.034 | -0.368 (-4.8%) | +0.139 (+2.8%) | | `sync.durable_write` | 15.356 / 20.181 | 20.790 / 28.655 | 19.925 / 24.211 | 15.394 / 18.268 | -4.444 (-15.5%) | -1.913 (-9.5%) | The dev pre/post controls remained close on round-trip and save acknowledgement. The PR medians show lower p50s on all three paths, lower p95s on save acknowledgement and durable write, and a `0.008 ms` round-trip p95 increase (within the run-to-run spread). No material Notes hot-path regression appeared in this ABAB run.
Author
Owner

Gate status update

The earlier focused-gates table marked Notes hot-path ABAB ×5 pending. It completed on 2026-11-08 at the same epoch with five dev controls before and after and five A-B-A-B rounds; the full table, hashes, and run details are in comment #86506.

At latest head 40d588eee, Whiteboard first paint remains 0 KiB JS / 0 KiB CSS. The English default open path measures 264.016 KiB JS / 18.010 KiB CSS; the full optional closure measures 2,036.322 KiB across 156 JS chunks. Lazy budgets use measured per-chunk lines with initial: false; no aggregate Whiteboard total was added. The top 15 module table and slice 2 split experiment are in comment #86444.

The legacy_attachment_migration_is_recoverable_at_each_boundary gate remains pending on the stable Files identity API from #1010.

## Gate status update The earlier focused-gates table marked Notes hot-path ABAB ×5 pending. It completed on 2026-11-08 at the same epoch with five dev controls before and after and five A-B-A-B rounds; the full table, hashes, and run details are in [comment #86506](https://git.kayg.org/kayg/calternal/pulls/1015#issuecomment-86506). At latest head `40d588eee`, Whiteboard first paint remains 0 KiB JS / 0 KiB CSS. The English default open path measures 264.016 KiB JS / 18.010 KiB CSS; the full optional closure measures 2,036.322 KiB across 156 JS chunks. Lazy budgets use measured per-chunk lines with `initial: false`; no aggregate Whiteboard total was added. The top 15 module table and slice 2 split experiment are in [comment #86444](https://git.kayg.org/kayg/calternal/pulls/1015#issuecomment-86444). The `legacy_attachment_migration_is_recoverable_at_each_boundary` gate remains pending on the stable Files identity API from #1010.
Some checks failed
CI / server (pull_request) Has been cancelled
CI / rust (pull_request) Has been cancelled
CI / web (pull_request) Has been cancelled
Markdown extraction contract / markdown-native-contract (pull_request) Has been cancelled
Markdown extraction contract / markdown-editor-contract (pull_request) Has been cancelled
This pull request is marked as a work in progress.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feat/whiteboard-s1:feat/whiteboard-s1
git switch feat/whiteboard-s1

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch dev
git merge --no-ff feat/whiteboard-s1
git switch feat/whiteboard-s1
git rebase dev
git switch dev
git merge --ff-only feat/whiteboard-s1
git switch feat/whiteboard-s1
git rebase dev
git switch dev
git merge --no-ff feat/whiteboard-s1
git switch dev
git merge --squash feat/whiteboard-s1
git switch dev
git merge --ff-only feat/whiteboard-s1
git switch dev
git merge feat/whiteboard-s1
git push origin dev
Sign in to join this conversation.
No reviewers
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!1015
No description provided.