CANVAS: files over app — images and files on a canvas are linked Home files, never embedded (§60) #989

Open
opened 2026-10-03 07:10:07 +00:00 by kayg · 10 comments
Owner

Owner decision (2026-10-03, #509 C6)

"No base64, nothing embedded directly inside excalidraw. Everything is a file (file over app) that is linked (and rendered live)." Contract: DESIGN §60 "Files over app". Builds on #976 (Canvas core).

Scope

  • Dropping, pasting or picking an image, PDF or other file onto a canvas uploads it through Files into the canvas's folder (or the Notes attachments rule if one exists; reuse it), then adds an Excalidraw image element whose customData holds the stable calternal item ID, plus a <40-hex fileId>: [[name]] line in the .excalidraw.md Embedded Files section. The JSON files map stays empty.
  • Rendering resolves by calternal ID first, by [[name]] second, and fixes the link text after a rename on the next save. Images render live (a changed file re-renders), only in view, from our origin, with thumbnails for large images.
  • Older files with inline base64 images open read-only-safe (opening never writes, #661). On the first real change, each inline image moves into a Home file next to the canvas and is replaced by a link, in the same atomic save. Plain .excalidraw files get the same treatment (a sidecar link list in customData), so no file keeps inline blobs after an edit.
  • Limits from the security review: type allowlist, magic-byte check, per-file cap, quota. Image bytes never enter the Yjs document or §61 history.
  • Parity: API/CLI/MCP/WebMCP add an image by item ID through the one event path.

Verification

e2e as a User: drop a photo, see it on the canvas and in Photos; rename the photo in Files, the canvas still shows it; open a fixture with inline base64, edit one shape, see the image extracted to a file and the canvas file without base64. Screenshots 390/820/1440 light/dark.

## Owner decision (2026-10-03, #509 C6) "No base64, nothing embedded directly inside excalidraw. Everything is a file (file over app) that is linked (and rendered live)." Contract: DESIGN §60 "Files over app". Builds on #976 (Canvas core). ## Scope - Dropping, pasting or picking an image, PDF or other file onto a canvas uploads it through Files into the canvas's folder (or the Notes attachments rule if one exists; reuse it), then adds an Excalidraw image element whose `customData` holds the stable calternal item ID, plus a `<40-hex fileId>: [[name]]` line in the `.excalidraw.md` Embedded Files section. The JSON `files` map stays empty. - Rendering resolves by calternal ID first, by `[[name]]` second, and fixes the link text after a rename on the next save. Images render live (a changed file re-renders), only in view, from our origin, with thumbnails for large images. - Older files with inline base64 images open read-only-safe (opening never writes, #661). On the first real change, each inline image moves into a Home file next to the canvas and is replaced by a link, in the same atomic save. Plain `.excalidraw` files get the same treatment (a sidecar link list in `customData`), so no file keeps inline blobs after an edit. - Limits from the security review: type allowlist, magic-byte check, per-file cap, quota. Image bytes never enter the Yjs document or §61 history. - Parity: API/CLI/MCP/WebMCP add an image by item ID through the one event path. ## Verification e2e as a User: drop a photo, see it on the canvas and in Photos; rename the photo in Files, the canvas still shows it; open a fixture with inline base64, edit one shape, see the image extracted to a file and the canvas file without base64. Screenshots 390/820/1440 light/dark.
Author
Owner

Starting #989 on job/canvas-files-989, Canvas core base beb3bbf4b; origin/dev merged, current HEAD b761f091d3b192279ccf8291ed5cd124ea564da6. Read CLAUDE.md, CONTEXT.md and DESIGN §60. Reusing the core whole-element event path and Files upload/identity APIs. No dependency changes planned. Verification follows the per-branch policy.

Starting #989 on `job/canvas-files-989`, Canvas core base `beb3bbf4b`; origin/dev merged, current HEAD `b761f091d3b192279ccf8291ed5cd124ea564da6`. Read CLAUDE.md, CONTEXT.md and DESIGN §60. Reusing the core whole-element event path and Files upload/identity APIs. No dependency changes planned. Verification follows the per-branch policy.
Author
Owner

Finding: the Canvas collaboration document stores elements and settings only (calternal-collab/src/canvas.rs::from_scene); inline image bytes already stay outside Yrs. The codec now reads/writes portable Embedded Files references.

Blocked part: atomic legacy inline extraction needs one crash-recoverable transaction that creates several Home assets and conditionally replaces the Canvas. calternal-fs/src/write.rs exposes write and replace_if for one file; journal.rs recovers one file installation. There is no multi-file save primitive. Per the job rule to stop when another crate behavior/design must change, this job does not add a new filesystem transaction design. Linked uploads and rendering remain independent work. READY FOR MERGE will remain no while this requirement is absent.

Decision: reserve customData.calternalFile = {itemId, name} for the stable Files identity and portable fallback. fileId is 40 hex characters. An inaccessible stored identity never falls back to a different file at its old name. No dependency additions.

Finding: the Canvas collaboration document stores elements and settings only (`calternal-collab/src/canvas.rs::from_scene`); inline image bytes already stay outside Yrs. The codec now reads/writes portable Embedded Files references. Blocked part: atomic legacy inline extraction needs one crash-recoverable transaction that creates several Home assets and conditionally replaces the Canvas. `calternal-fs/src/write.rs` exposes `write` and `replace_if` for one file; `journal.rs` recovers one file installation. There is no multi-file save primitive. Per the job rule to stop when another crate behavior/design must change, this job does not add a new filesystem transaction design. Linked uploads and rendering remain independent work. READY FOR MERGE will remain no while this requirement is absent. Decision: reserve `customData.calternalFile = {itemId, name}` for the stable Files identity and portable fallback. `fileId` is 40 hex characters. An inaccessible stored identity never falls back to a different file at its old name. No dependency additions.
Author
Owner

Focused production regression: a genuine PNG picker upload creates a Home file, the Canvas image renders its pixels, files stays {}, and the Canvas ETag stays unchanged on reopen. Files rename keeps the item ID and resolves the new path, but the next drawing save retained [[Canvas photo.png]] instead of [[Renamed photo.png]]. The asset metadata refetch had not finished before the geometry event. A name repair is now armed by that actual edit and completes when metadata arrives; rename hints and open do not arm writes. Verification is running.

The new test initially expected HTTP 200 from Files rename. I corrected this newly introduced assertion to the existing API contract, HTTP 204. No pre-existing test assertion or fixture changed.

Focused production regression: a genuine PNG picker upload creates a Home file, the Canvas image renders its pixels, `files` stays `{}`, and the Canvas ETag stays unchanged on reopen. Files rename keeps the item ID and resolves the new path, but the next drawing save retained `[[Canvas photo.png]]` instead of `[[Renamed photo.png]]`. The asset metadata refetch had not finished before the geometry event. A name repair is now armed by that actual edit and completes when metadata arrives; rename hints and open do not arm writes. Verification is running. The new test initially expected HTTP 200 from Files rename. I corrected this newly introduced assertion to the existing API contract, HTTP 204. No pre-existing test assertion or fixture changed.
Author
Owner

READY FOR MERGE: no

Head: 8efef00fac (job/canvas-files-989).
Origin/dev was merged at start; the single final fetch/merge found it up to date. No push or deployment. Work is committed in small slices.

Built

  • Add files, pointer drop and file paste use Files uploads into the Canvas folder. Image elements carry stable Home item IDs. Embedded Files links survive rename; portable files maps stay empty for new uploads.
  • Load linked images in view through origin URLs, with four concurrent resolutions. Files events refresh metadata. Renderer IDs and image fetch states stay out of shared events. The server rejects inline data URLs in submitted elements.
  • Added unit coverage, a production regression and a performance profile. The regression checks picker/drop, Photos indexing, opening without a write, drawing after image load, rename repair and actual image pixels at all six widths/themes.

Files

  • crates/calternal-notes-core/src/canvas.rs
  • crates/calternal-collab/src/canvas.rs
  • apps/web/src/lib/canvas/CanvasReact.tsx
  • apps/web/src/lib/canvas/CanvasView.svelte
  • apps/web/src/lib/canvas/files.ts
  • apps/web/src/lib/canvas/files.test.ts
  • apps/web/src/lib/canvas/scene.ts
  • apps/web/e2e/canvas-files-989.mjs
  • bench/canvas-files-989.mjs

UX gaps closed

  • Opening a linked image no longer writes its renderer fetch status to the source.
  • Asset arrival preserves an active drawing. A rename that resolves after a real edit still repairs the next saved link.
  • Normal opening fits existing content on phone; an element deep link takes priority.
  • Undo removes a newly inserted drawing element while keeping its Home file.
  • Picker and drop create real Home files that appear in Photos.

Known gaps / UX gaps left

  • Atomic legacy extraction on the first edit is not built. Legacy source opens unchanged, but still retains its inline data on edit. calternal-fs exposes single-file journal writes, not the required multi-file transaction. Adding that transaction changes another crate's behavior/design, outside the permitted minimal public additions. This is a blocking requirement, recorded in the earlier finding comment.
  • Linked assets are not yet supplied to note embeds or the server export renderer. They still depend on the source files map. These surfaces are incomplete.
  • Canvas-specific server upload magic/type/cap checks are incomplete. Existing Files quota enforcement is reused; client size/type guards and event data-URL rejection do not replace that requirement.
  • PDF/other file previews, clipboard uploads, public/share permissions and content replacement have not had end-to-end verification. Renderer revision IDs remain cached until unmount. Legacy name-only identity adoption and plain JSON sidecar migration remain incomplete.

Decisions

  • Store the reference as customData.calternalFile={itemId,name}, with a random 40-hex portable fileId. Resolve the stable ID first. If access to that ID fails, do not fall back to a different file by name.
  • Upload beside the Canvas. No dedicated Notes attachment-folder rule was found. Use a 320×240 initial image rectangle at the captured drop position (or the view insertion position).
  • Reuse a 10 MiB client cap and origin image loading. Use available thumbnails for documents and larger images; do not directly decode SVG.
  • Fit content on normal open, with element deep links taking priority. Use temporary renderer revision IDs to bypass Excalidraw's same-ID file cache, and restore portable IDs before events.

Verification
No dependency versions were changed. Doc comments in all touched files were re-read. No pre-existing test expectation was changed. Two new regression expectations were corrected during development: rename uses the existing API's 204 status; dark image pixels include the pinned renderer's inverse-image filter.

cargo fmt --check: exit 0, no output.
Rust commands used CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4 and the worktree target/tmp.

cargo clippy -p calternal-notes-core --all-targets -- -D warnings

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

cargo test -p calternal-notes-core

test result: ok. 539 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.69s
test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.33s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.45s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-collab --all-targets -- -D warnings

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

cargo test -p calternal-collab

test result: ok. 45 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.49s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.82s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.78s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 69.87s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.00s
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 2.10s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.39s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.18s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 31.97s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.18s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 26.01s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-server --all-targets -- -D warnings

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

cargo test -p calternal-server

test result: ok. 162 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 15.82s

cd apps/web && bun run check

svelte-check found 0 errors and 0 warnings

cd apps/web && bunx vitest run src/lib/canvas/files.test.ts src/lib/canvas/scene.test.ts --maxWorkers=2

 Test Files  2 passed (2)
      Tests  21 passed (21)

Production build passed. cd apps/web && bun e2e/canvas-files-989.mjs:

PASS picker and drop uploads, Photos indexing and portable Home links
PASS real image pixels and read-only-safe opening
PASS real drawing action
Captured macOS 390 light
Captured macOS 390 dark
Captured macOS 820 light
Captured macOS 820 dark
Captured macOS 1440 light
Captured macOS 1440 dark
PASS #989: picker upload, real image pixels, stable rename, portable links, six production screenshots

Screenshots
Real production build, macOS platform emulation; attached for Claude visual review.

Performance
Profile added for 10 and 500 linked Home assets, five opens, pan and a 16-request burst. It records p50/p95, server CPU/RSS and load average. No Canvas baseline exists in docs/perf/baseline.json. No measurement was run: the latest verification policy reserves perf VM measurements for performance issues. Numbers remain pending.

For the merge round
These checks do not complete the missing implementation above. Run after those requirements are completed:

  • cd apps/web && bun run test --maxWorkers=2: full web regression suite.
  • bash tests/adversarial/run.sh: full robustness, XUser and authorization matrices; check linked-file denial and hostile event/upload inputs.
  • cd apps/web && bun run test:e2e --maxWorkers=2: existing full shell e2e entry point; browser runners must remain single-browser.
  • cd apps/web && bun e2e/canvas-files-989.mjs: focused production upload/rename/pixel evidence against the combined build.
  • On the perf VM, with the shared release binary set in CALTERNAL_SERVER_BIN: flock /root/perf.lock bun bench/canvas-files-989.mjs --json artifacts/canvas-files-989-profile.json. The profile records load average inside the lock. Establish the missing baseline and compare average/worst cases.
  • Add/run legacy extraction and embed/export image regressions when those implementations exist.

Build output is being removed with cargo clean and removal of apps/web/build and apps/web/.svelte-kit/output. Review screenshots and logs remain under artifacts; none are committed.

READY FOR MERGE: no Head: 8efef00fac8090638211d673bda01bdb0abea5de (job/canvas-files-989). Origin/dev was merged at start; the single final fetch/merge found it up to date. No push or deployment. Work is committed in small slices. Built - Add files, pointer drop and file paste use Files uploads into the Canvas folder. Image elements carry stable Home item IDs. Embedded Files links survive rename; portable files maps stay empty for new uploads. - Load linked images in view through origin URLs, with four concurrent resolutions. Files events refresh metadata. Renderer IDs and image fetch states stay out of shared events. The server rejects inline data URLs in submitted elements. - Added unit coverage, a production regression and a performance profile. The regression checks picker/drop, Photos indexing, opening without a write, drawing after image load, rename repair and actual image pixels at all six widths/themes. Files - crates/calternal-notes-core/src/canvas.rs - crates/calternal-collab/src/canvas.rs - apps/web/src/lib/canvas/CanvasReact.tsx - apps/web/src/lib/canvas/CanvasView.svelte - apps/web/src/lib/canvas/files.ts - apps/web/src/lib/canvas/files.test.ts - apps/web/src/lib/canvas/scene.ts - apps/web/e2e/canvas-files-989.mjs - bench/canvas-files-989.mjs UX gaps closed - Opening a linked image no longer writes its renderer fetch status to the source. - Asset arrival preserves an active drawing. A rename that resolves after a real edit still repairs the next saved link. - Normal opening fits existing content on phone; an element deep link takes priority. - Undo removes a newly inserted drawing element while keeping its Home file. - Picker and drop create real Home files that appear in Photos. Known gaps / UX gaps left - Atomic legacy extraction on the first edit is not built. Legacy source opens unchanged, but still retains its inline data on edit. calternal-fs exposes single-file journal writes, not the required multi-file transaction. Adding that transaction changes another crate's behavior/design, outside the permitted minimal public additions. This is a blocking requirement, recorded in the earlier finding comment. - Linked assets are not yet supplied to note embeds or the server export renderer. They still depend on the source files map. These surfaces are incomplete. - Canvas-specific server upload magic/type/cap checks are incomplete. Existing Files quota enforcement is reused; client size/type guards and event data-URL rejection do not replace that requirement. - PDF/other file previews, clipboard uploads, public/share permissions and content replacement have not had end-to-end verification. Renderer revision IDs remain cached until unmount. Legacy name-only identity adoption and plain JSON sidecar migration remain incomplete. Decisions - Store the reference as customData.calternalFile={itemId,name}, with a random 40-hex portable fileId. Resolve the stable ID first. If access to that ID fails, do not fall back to a different file by name. - Upload beside the Canvas. No dedicated Notes attachment-folder rule was found. Use a 320×240 initial image rectangle at the captured drop position (or the view insertion position). - Reuse a 10 MiB client cap and origin image loading. Use available thumbnails for documents and larger images; do not directly decode SVG. - Fit content on normal open, with element deep links taking priority. Use temporary renderer revision IDs to bypass Excalidraw's same-ID file cache, and restore portable IDs before events. Verification No dependency versions were changed. Doc comments in all touched files were re-read. No pre-existing test expectation was changed. Two new regression expectations were corrected during development: rename uses the existing API's 204 status; dark image pixels include the pinned renderer's inverse-image filter. cargo fmt --check: exit 0, no output. Rust commands used CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4 and the worktree target/tmp. `cargo clippy -p calternal-notes-core --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.66s ``` `cargo test -p calternal-notes-core` ```text test result: ok. 539 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.69s test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.33s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.03s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.45s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-collab --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 4.99s ``` `cargo test -p calternal-collab` ```text test result: ok. 45 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.49s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.82s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 7.78s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 69.87s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 8.00s 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 2.10s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.39s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.18s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 31.97s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.18s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 26.01s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-server --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 17.97s ``` `cargo test -p calternal-server` ```text test result: ok. 162 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 15.82s ``` `cd apps/web && bun run check` ```text svelte-check found 0 errors and 0 warnings ``` `cd apps/web && bunx vitest run src/lib/canvas/files.test.ts src/lib/canvas/scene.test.ts --maxWorkers=2` ```text Test Files 2 passed (2) Tests 21 passed (21) ``` Production build passed. `cd apps/web && bun e2e/canvas-files-989.mjs`: ```text PASS picker and drop uploads, Photos indexing and portable Home links PASS real image pixels and read-only-safe opening PASS real drawing action Captured macOS 390 light Captured macOS 390 dark Captured macOS 820 light Captured macOS 820 dark Captured macOS 1440 light Captured macOS 1440 dark PASS #989: picker upload, real image pixels, stable rename, portable links, six production screenshots ``` Screenshots Real production build, macOS platform emulation; attached for Claude visual review. - [1440-dark-linked-image.png](https://git.kayg.org/attachments/50810001-3c1e-4a46-82f3-24e84aa69d5c) - [1440-light-linked-image.png](https://git.kayg.org/attachments/e3668571-a10e-4401-907a-7c9afba29517) - [390-dark-linked-image.png](https://git.kayg.org/attachments/02cb4a5e-fccc-4316-9b2f-811eb8e7d981) - [390-light-linked-image.png](https://git.kayg.org/attachments/ad006aef-e8b0-4886-8706-9fb7c532a76e) - [820-dark-linked-image.png](https://git.kayg.org/attachments/e362bc0e-9d80-4c50-94b7-a90f32f6d7ea) - [820-light-linked-image.png](https://git.kayg.org/attachments/2e658e2c-58ad-44ea-bade-a9ce0a473094) Performance Profile added for 10 and 500 linked Home assets, five opens, pan and a 16-request burst. It records p50/p95, server CPU/RSS and load average. No Canvas baseline exists in docs/perf/baseline.json. No measurement was run: the latest verification policy reserves perf VM measurements for performance issues. Numbers remain pending. For the merge round These checks do not complete the missing implementation above. Run after those requirements are completed: - `cd apps/web && bun run test --maxWorkers=2`: full web regression suite. - `bash tests/adversarial/run.sh`: full robustness, XUser and authorization matrices; check linked-file denial and hostile event/upload inputs. - `cd apps/web && bun run test:e2e --maxWorkers=2`: existing full shell e2e entry point; browser runners must remain single-browser. - `cd apps/web && bun e2e/canvas-files-989.mjs`: focused production upload/rename/pixel evidence against the combined build. - On the perf VM, with the shared release binary set in CALTERNAL_SERVER_BIN: `flock /root/perf.lock bun bench/canvas-files-989.mjs --json artifacts/canvas-files-989-profile.json`. The profile records load average inside the lock. Establish the missing baseline and compare average/worst cases. - Add/run legacy extraction and embed/export image regressions when those implementations exist. Build output is being removed with cargo clean and removal of apps/web/build and apps/web/.svelte-kit/output. Review screenshots and logs remain under artifacts; none are committed.
Author
Owner

Round 2 started on job/canvas-files-989, base/head 8efef00fa. I will address legacy extraction on the first real edit, linked assets in Note previews and exports, and server validation. Opening must preserve source bytes. Focused gates run here; full adversarial matrices and suites remain for the merge round under the current verification policy.

Round 2 started on `job/canvas-files-989`, base/head `8efef00fa`. I will address legacy extraction on the first real edit, linked assets in Note previews and exports, and server validation. Opening must preserve source bytes. Focused gates run here; full adversarial matrices and suites remain for the merge round under the current verification policy.
Author
Owner

Round 2 findings and implementation:

  • Note previews passed only source.files; linked Home images therefore had no pixels. The committed preview fix resolves IDs through Files without writing source.
  • Server exports passed the same empty map to the offline renderer. The new Files adapter resolves authorized immutable reads into an ephemeral export scene. PNG/SVG metadata embeds that scene, with Home-specific references removed in the export only.
  • Canvas uploads used general Tus validation. New canvas-mime metadata persists across resume/restart; cap/type checks happen at creation, full magic/passive-SVG checks happen before install, and quota uses existing Tus reservations.
  • A linked file can change through other clients. Canvas image downloads now request a bounded validated representation through the existing Files download authorization path; active SVG or a disguised replacement fails before bytes reach Excalidraw.

Decisions: legacy extraction fsyncs content-derived Home assets first, then uses the existing Canvas compare-and-swap as its commit point. An interruption leaves the old Canvas intact (possibly with completed, unlinked assets); retry reuses matching assets. A committed Canvas cannot reference a not-yet-written asset. Existing unrelated content at the deterministic name is never overwritten. PDF drawing exports use the existing first-page thumbnail. The Canvas upload allowlist is PNG, JPEG, WebP, GIF, passive SVG and PDF.

Round 2 findings and implementation: - Note previews passed only `source.files`; linked Home images therefore had no pixels. The committed preview fix resolves IDs through Files without writing source. - Server exports passed the same empty map to the offline renderer. The new Files adapter resolves authorized immutable reads into an ephemeral export scene. PNG/SVG metadata embeds that scene, with Home-specific references removed in the export only. - Canvas uploads used general Tus validation. New `canvas-mime` metadata persists across resume/restart; cap/type checks happen at creation, full magic/passive-SVG checks happen before install, and quota uses existing Tus reservations. - A linked file can change through other clients. Canvas image downloads now request a bounded validated representation through the existing Files download authorization path; active SVG or a disguised replacement fails before bytes reach Excalidraw. Decisions: legacy extraction fsyncs content-derived Home assets first, then uses the existing Canvas compare-and-swap as its commit point. An interruption leaves the old Canvas intact (possibly with completed, unlinked assets); retry reuses matching assets. A committed Canvas cannot reference a not-yet-written asset. Existing unrelated content at the deterministic name is never overwritten. PDF drawing exports use the existing first-page thumbnail. The Canvas upload allowlist is PNG, JPEG, WebP, GIF, passive SVG and PDF.
Author
Owner

Round 2 finding: the real macOS browser regression confirms linked Canvas and Note preview pixels at 390/820/1440 in light/dark. First-edit legacy extraction succeeds. Undo is enabled after drawing, but leaves the new rectangle present. The renderer history uses its creation ID; the existing boundary changed that ID inside the renderer. The fix keeps native IDs in renderer history and projects stable IDs/bindings only for events and links, with the reverse projection for server echoes. Focused binding/Undo projection coverage is added. The Home namespace lock now also covers asset preparation through Canvas CAS, in the same order as Files/Notes mutations.

Round 2 finding: the real macOS browser regression confirms linked Canvas and Note preview pixels at 390/820/1440 in light/dark. First-edit legacy extraction succeeds. Undo is enabled after drawing, but leaves the new rectangle present. The renderer history uses its creation ID; the existing boundary changed that ID inside the renderer. The fix keeps native IDs in renderer history and projects stable IDs/bindings only for events and links, with the reverse projection for server echoes. Focused binding/Undo projection coverage is added. The Home namespace lock now also covers asset preparation through Canvas CAS, in the same order as Files/Notes mutations.
Author
Owner

READY FOR MERGE: yes

Head: f398c64851 (job/canvas-files-989). Round 2 starts from 8efef00fa. The single final fetch/merge of origin/dev is committed as 7bb4328bd. No push or deployment.

Built

  • Extract all legacy inline images on the first real Canvas change, for JSON and Markdown. The Home files are durable before one checked Canvas replacement publishes the links. The Home mutation lock stays held through that replacement. Fault injection after each asset installation, reopening the Root and retrying verifies the old source survives interruption and the committed scene has complete assets.
  • Resolve linked Home images in live Note previews. PNG/SVG exports embed the linked bytes and remove Home-specific references in the export copy only. Opening and exporting do not edit the source.
  • Validate Canvas uploads on the server: PNG, JPEG, WebP, GIF, passive SVG and PDF; matching filename/type; magic/container boundaries; 10 MiB per file; existing Home quota and Tus reservations. The declared type persists across resume/restart. Canvas image reads validate again after another client replaces a file.
  • Preserve legacy images through Undo and keep native renderer identities so Undo can remove newly drawn elements. Stable identities and bindings are projected at the event/link boundary. Extraction and recovery scans are linear; preview refresh bursts coalesce.

UX gaps closed

  • Linked images now appear in Note previews and exports.
  • Opening, image resolution and previews no longer require a save.
  • First-edit extraction retains opaque fields and tombstones, and Undo retains the Home image.
  • Undo of a new drawing now addresses the renderer's original identity.
  • Invalid uploads release staged chunks, their durable row and quota reservation. A changed SVG cannot bypass the Canvas image validation.

Known gaps / UX gaps left

  • Interrupted preparation can leave completed, unlinked Home assets while the old Canvas stays intact. Retry reuses those files. They are not deleted automatically because another Canvas may use them.
  • Broad Share/public-link matrices, the full Canvas cross-client suite and real Apple-client checks belong to the merge round. This job verifies the focused local owner flows and emulated macOS rendering.
  • No performance measurements were run. The latest verification policy permits them only for a performance issue. The extended profile covers opening, Note previews, export and bursts at 10 and 500 linked images. No linked-asset baseline exists in docs/perf/baseline.json.

Decisions

  • Use durable preparation plus the existing Canvas CAS as the commit point. No calternal-fs transaction API is changed. Hold the namespace lock before the Notes lock, then through CAS.
  • Save extracted images beside the Canvas with content-derived names and a deterministic 40-hex portable file ID. Reuse identical files; never replace unrelated content at the same name.
  • Export PDFs as the existing authorized first-page image. Export copies include image bytes and omit customData.calternalFile so they reopen without Home access.
  • Resolve preview assets sequentially, with a 32 MiB aggregate bound.

Files

  • apps/web/e2e/canvas-976.mjs
  • apps/web/e2e/canvas-files-989.mjs
  • apps/web/src/lib/canvas/CanvasPreview.svelte
  • apps/web/src/lib/canvas/CanvasReact.tsx
  • apps/web/src/lib/canvas/elementIds.test.ts
  • apps/web/src/lib/canvas/elementIds.ts
  • apps/web/src/lib/canvas/files.test.ts
  • apps/web/src/lib/canvas/files.ts
  • apps/web/src/lib/files/uploads.svelte.test.ts
  • apps/web/src/lib/files/uploads.svelte.ts
  • bench/canvas-files-989.mjs
  • crates/calternal-collab/src/canvas.rs
  • crates/calternal-collab/src/session.rs
  • crates/calternal-notes-core/src/canvas.rs
  • crates/calternal-server/src/wire.rs
  • crates/plugins/files/migrations/0021_canvas_upload_type.sql
  • crates/plugins/files/src/canvas_assets.rs
  • crates/plugins/files/src/lib.rs
  • crates/plugins/files/src/uploads.rs
  • crates/plugins/notes/src/canvas_assets.rs
  • crates/plugins/notes/src/canvas_export.rs
  • crates/plugins/notes/src/lib.rs
  • tests/adversarial/canvas_assets_989.mjs

Verification
No dependencies changed. Files migration 0021 was free on origin/dev before final gates. Doc comments in the changed modules were re-read. Two existing expectations change because #989 changes their behavior: migration maximum 20 becomes 21, and a first edit extracts the inline fixture instead of retaining its binary map.

Cargo used CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4 and the worktree target/tmp. cargo fmt --check: exit 0, no output.

cargo clippy -p calternal-notes-core --all-targets -- -D warnings

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

cargo test -p calternal-notes-core -- --test-threads=4

test result: ok. 540 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.42s
test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.36s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.61s
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings

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

cargo test -p calternal-plugin-notes -- --test-threads=4

test result: ok. 194 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 180.28s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.44s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-plugin-files --all-targets -- -D warnings

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

cargo test -p calternal-plugin-files -- --test-threads=4

test result: ok. 161 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 123.29s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-collab --all-targets -- -D warnings

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

cargo test -p calternal-collab -- --test-threads=4

test result: ok. 46 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.33s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.20s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 37.00s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.02s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.86s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.95s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.38s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.56s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.41s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.76s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo clippy -p calternal-server --all-targets -- -D warnings

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

cargo test -p calternal-server -- --test-threads=4

test result: ok. 162 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 14.09s

cd apps/web && bun run check

svelte-check found 0 errors and 0 warnings

cd apps/web && bunx vitest run src/lib/canvas/elementIds.test.ts src/lib/canvas/files.test.ts src/lib/files/uploads.svelte.test.ts --maxWorkers=2

 Test Files  3 passed (3)
      Tests  25 passed (25)

Production web build: exit 0. The pinned Excalidraw subset worker emits the existing EMPTY_IMPORT_META warning. Focused runtime output and screenshots follow below.

CANVAS_989_SERVER_CHECKS=1 bun apps/web/e2e/canvas-files-989.mjs (final job server binary, production web build, local HTTPS front):

PASS picker and drop uploads, Photos indexing and portable Home links
PASS real image pixels and read-only-safe opening
Renamed metadata Renamed photo.png
PASS real drawing action
Captured macOS 390 light
Captured macOS 390 dark
Captured macOS 820 light
Captured macOS 820 dark
Captured macOS 1440 light
Captured macOS 1440 dark
PASS first-edit extraction, opening without writes and keyboard Undo (JSON)
PASS first-edit extraction, opening without writes and keyboard Undo (Markdown)
PASS PNG/SVG export scenes embed linked image bytes without Home references
PASS #989 local asset rejection and reservation release
PASS #989: picker upload, real image pixels, stable rename, portable links, linked Note previews and twelve production screenshots

Screenshot evidence: production Canvas and live Note preview, macOS emulation, 390/820/1440 px, light/dark. Screenshots are attached; no review artifact is committed. Claude remains the visual reviewer.

For the merge round

  • (cd apps/web && bun run test --maxWorkers=2) — full web regressions on the combined branch.
  • bun apps/web/e2e/canvas-976.mjs — cross-client Canvas edits, fonts, deep links, partial embeds and imported/exported format preservation.
  • CANVAS_989_SERVER_CHECKS=1 bun apps/web/e2e/canvas-files-989.mjs — preserve these owner flows on the combined server/web build.
  • bash tests/adversarial/run.sh — one time-boxed full robustness, XUser and authorization round, including cross-plugin interactions.
  • If a performance round is scheduled on the perf VM with the shared release binary set in CALTERNAL_SERVER_BIN: flock /root/perf.lock bash -lc 'uptime; test -n "$CALTERNAL_SERVER_BIN" && bun bench/canvas-files-989.mjs --json artifacts/canvas-files-989-profile.json' — record p50/p95, CPU, RSS and the burst/500-image cases. No build on that VM.

Cleanup: cargo clean completed and web/renderer build output was removed. The worktree is clean. No push, deployment or additional merge.

READY FOR MERGE: yes Head: f398c64851ef8261f4e2bc0828bd82abeb2e6b63 (`job/canvas-files-989`). Round 2 starts from `8efef00fa`. The single final fetch/merge of origin/dev is committed as `7bb4328bd`. No push or deployment. Built - Extract all legacy inline images on the first real Canvas change, for JSON and Markdown. The Home files are durable before one checked Canvas replacement publishes the links. The Home mutation lock stays held through that replacement. Fault injection after each asset installation, reopening the Root and retrying verifies the old source survives interruption and the committed scene has complete assets. - Resolve linked Home images in live Note previews. PNG/SVG exports embed the linked bytes and remove Home-specific references in the export copy only. Opening and exporting do not edit the source. - Validate Canvas uploads on the server: PNG, JPEG, WebP, GIF, passive SVG and PDF; matching filename/type; magic/container boundaries; 10 MiB per file; existing Home quota and Tus reservations. The declared type persists across resume/restart. Canvas image reads validate again after another client replaces a file. - Preserve legacy images through Undo and keep native renderer identities so Undo can remove newly drawn elements. Stable identities and bindings are projected at the event/link boundary. Extraction and recovery scans are linear; preview refresh bursts coalesce. UX gaps closed - Linked images now appear in Note previews and exports. - Opening, image resolution and previews no longer require a save. - First-edit extraction retains opaque fields and tombstones, and Undo retains the Home image. - Undo of a new drawing now addresses the renderer's original identity. - Invalid uploads release staged chunks, their durable row and quota reservation. A changed SVG cannot bypass the Canvas image validation. Known gaps / UX gaps left - Interrupted preparation can leave completed, unlinked Home assets while the old Canvas stays intact. Retry reuses those files. They are not deleted automatically because another Canvas may use them. - Broad Share/public-link matrices, the full Canvas cross-client suite and real Apple-client checks belong to the merge round. This job verifies the focused local owner flows and emulated macOS rendering. - No performance measurements were run. The latest verification policy permits them only for a performance issue. The extended profile covers opening, Note previews, export and bursts at 10 and 500 linked images. No linked-asset baseline exists in docs/perf/baseline.json. Decisions - Use durable preparation plus the existing Canvas CAS as the commit point. No calternal-fs transaction API is changed. Hold the namespace lock before the Notes lock, then through CAS. - Save extracted images beside the Canvas with content-derived names and a deterministic 40-hex portable file ID. Reuse identical files; never replace unrelated content at the same name. - Export PDFs as the existing authorized first-page image. Export copies include image bytes and omit customData.calternalFile so they reopen without Home access. - Resolve preview assets sequentially, with a 32 MiB aggregate bound. Files - `apps/web/e2e/canvas-976.mjs` - `apps/web/e2e/canvas-files-989.mjs` - `apps/web/src/lib/canvas/CanvasPreview.svelte` - `apps/web/src/lib/canvas/CanvasReact.tsx` - `apps/web/src/lib/canvas/elementIds.test.ts` - `apps/web/src/lib/canvas/elementIds.ts` - `apps/web/src/lib/canvas/files.test.ts` - `apps/web/src/lib/canvas/files.ts` - `apps/web/src/lib/files/uploads.svelte.test.ts` - `apps/web/src/lib/files/uploads.svelte.ts` - `bench/canvas-files-989.mjs` - `crates/calternal-collab/src/canvas.rs` - `crates/calternal-collab/src/session.rs` - `crates/calternal-notes-core/src/canvas.rs` - `crates/calternal-server/src/wire.rs` - `crates/plugins/files/migrations/0021_canvas_upload_type.sql` - `crates/plugins/files/src/canvas_assets.rs` - `crates/plugins/files/src/lib.rs` - `crates/plugins/files/src/uploads.rs` - `crates/plugins/notes/src/canvas_assets.rs` - `crates/plugins/notes/src/canvas_export.rs` - `crates/plugins/notes/src/lib.rs` - `tests/adversarial/canvas_assets_989.mjs` Verification No dependencies changed. Files migration 0021 was free on origin/dev before final gates. Doc comments in the changed modules were re-read. Two existing expectations change because #989 changes their behavior: migration maximum 20 becomes 21, and a first edit extracts the inline fixture instead of retaining its binary map. Cargo used CARGO_PROFILE_DEV_DEBUG=line-tables-only, CARGO_INCREMENTAL=0, CARGO_BUILD_JOBS=4 and the worktree target/tmp. cargo fmt --check: exit 0, no output. `cargo clippy -p calternal-notes-core --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 3.84s ``` `cargo test -p calternal-notes-core -- --test-threads=4` ```text test result: ok. 540 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.42s test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.36s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.07s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.61s test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-plugin-notes --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 15.61s ``` `cargo test -p calternal-plugin-notes -- --test-threads=4` ```text test result: ok. 194 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 180.28s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.44s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-plugin-files --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 35.35s ``` `cargo test -p calternal-plugin-files -- --test-threads=4` ```text test result: ok. 161 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 123.29s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-collab --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 31.17s ``` `cargo test -p calternal-collab -- --test-threads=4` ```text test result: ok. 46 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.33s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.37s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.20s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 37.00s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 6.02s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.86s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.95s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.38s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.56s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.41s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.02s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 14.76s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo clippy -p calternal-server --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 57.32s ``` `cargo test -p calternal-server -- --test-threads=4` ```text test result: ok. 162 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 14.09s ``` `cd apps/web && bun run check` ```text svelte-check found 0 errors and 0 warnings ``` `cd apps/web && bunx vitest run src/lib/canvas/elementIds.test.ts src/lib/canvas/files.test.ts src/lib/files/uploads.svelte.test.ts --maxWorkers=2` ```text Test Files 3 passed (3) Tests 25 passed (25) ``` Production web build: exit 0. The pinned Excalidraw subset worker emits the existing EMPTY_IMPORT_META warning. Focused runtime output and screenshots follow below. `CANVAS_989_SERVER_CHECKS=1 bun apps/web/e2e/canvas-files-989.mjs` (final job server binary, production web build, local HTTPS front): ```text PASS picker and drop uploads, Photos indexing and portable Home links PASS real image pixels and read-only-safe opening Renamed metadata Renamed photo.png PASS real drawing action Captured macOS 390 light Captured macOS 390 dark Captured macOS 820 light Captured macOS 820 dark Captured macOS 1440 light Captured macOS 1440 dark PASS first-edit extraction, opening without writes and keyboard Undo (JSON) PASS first-edit extraction, opening without writes and keyboard Undo (Markdown) PASS PNG/SVG export scenes embed linked image bytes without Home references PASS #989 local asset rejection and reservation release PASS #989: picker upload, real image pixels, stable rename, portable links, linked Note previews and twelve production screenshots ``` Screenshot evidence: production Canvas and live Note preview, macOS emulation, 390/820/1440 px, light/dark. Screenshots are attached; no review artifact is committed. Claude remains the visual reviewer. - [1440-dark-linked-image.png](https://git.kayg.org/attachments/1bad916c-8f93-455d-ba53-b2ab189dd738) - [1440-dark-linked-note-preview.png](https://git.kayg.org/attachments/771c5316-bd6c-4507-988e-a4a3ac1f3004) - [1440-light-linked-image.png](https://git.kayg.org/attachments/324b488c-beaf-4589-bbb9-928dc8cd6e57) - [1440-light-linked-note-preview.png](https://git.kayg.org/attachments/2acf7c74-1a88-4fa9-9eb4-0847c8d32668) - [390-dark-linked-image.png](https://git.kayg.org/attachments/9af6b6b2-89db-4e6a-ab18-c4112976dcc5) - [390-dark-linked-note-preview.png](https://git.kayg.org/attachments/c3b62f95-ee61-4bfa-8eb0-6d2a10cb8624) - [390-light-linked-image.png](https://git.kayg.org/attachments/641acaa6-3742-4e27-87b1-6aa913715e15) - [390-light-linked-note-preview.png](https://git.kayg.org/attachments/f21be4f6-ab79-4e08-921c-bec90384787a) - [820-dark-linked-image.png](https://git.kayg.org/attachments/1b071443-722d-48e3-8cf9-4bcb581d2acd) - [820-dark-linked-note-preview.png](https://git.kayg.org/attachments/ee123729-15c3-439e-a17d-c9da7355a433) - [820-light-linked-image.png](https://git.kayg.org/attachments/87331b9b-16e4-42e1-a03d-06dfbc5b2a31) - [820-light-linked-note-preview.png](https://git.kayg.org/attachments/9cfac133-c4a2-4060-b248-47d1bffa19c6) For the merge round - `(cd apps/web && bun run test --maxWorkers=2)` — full web regressions on the combined branch. - `bun apps/web/e2e/canvas-976.mjs` — cross-client Canvas edits, fonts, deep links, partial embeds and imported/exported format preservation. - `CANVAS_989_SERVER_CHECKS=1 bun apps/web/e2e/canvas-files-989.mjs` — preserve these owner flows on the combined server/web build. - `bash tests/adversarial/run.sh` — one time-boxed full robustness, XUser and authorization round, including cross-plugin interactions. - If a performance round is scheduled on the perf VM with the shared release binary set in CALTERNAL_SERVER_BIN: `flock /root/perf.lock bash -lc 'uptime; test -n "$CALTERNAL_SERVER_BIN" && bun bench/canvas-files-989.mjs --json artifacts/canvas-files-989-profile.json'` — record p50/p95, CPU, RSS and the burst/500-image cases. No build on that VM. Cleanup: cargo clean completed and web/renderer build output was removed. The worktree is clean. No push, deployment or additional merge.
Author
Owner

Merge round 7c starts on job/merge-round-7c, base 4082669f7. The current owner job authorizes integration and full verification; it supersedes the original read-only #867 brief.

Canvas order: core → files → collab → Sketch → Pencil → cards. Migrations follow the combined 7b schema. No pushes or deploys. Final report will include verbatim gates, screenshots, defensive renderer review and staging readiness.

Merge round 7c starts on `job/merge-round-7c`, base `4082669f7`. The current owner job authorizes integration and full verification; it supersedes the original read-only #867 brief. Canvas order: core → files → collab → Sketch → Pencil → cards. Migrations follow the combined 7b schema. No pushes or deploys. Final report will include verbatim gates, screenshots, defensive renderer review and staging readiness.
Author
Owner

Finished merge-round-7c integration at 094d22e44507bf8bdd87dd8ffd460c254cb6329c. READY FOR STAGING: no.

Production Files flow passes: picker upload, real image pixels, stable rename, portable links and linked Note previews.

Production macOS evidence is attached to #867: 390/820/1440, light/dark. Screenshots remain outside git.

Focused Canvas viewer authority regression:

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 248 filtered out; finished in 2.52s

Svelte check:

svelte-check found 0 errors and 4 warnings in 3 files

The full report, renderer boundary review, migration upgrade evidence, exact gate excerpts and decisions are in docs/audits/merge-round-7c.md and the final #867 comment. Staging blockers include the Notes process SIGSEGV after 278 passing assertions (#1069), stale performance exception pins, the retained thumbnail test conflict, Sketch save and unfinished verification. No pushes or deployments.

Finished merge-round-7c integration at `094d22e44507bf8bdd87dd8ffd460c254cb6329c`. READY FOR STAGING: no. Production Files flow passes: picker upload, real image pixels, stable rename, portable links and linked Note previews. Production macOS evidence is attached to [#867](https://git.kayg.org/kayg/calternal/issues/867): 390/820/1440, light/dark. Screenshots remain outside git. Focused Canvas viewer authority regression: ```text test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 248 filtered out; finished in 2.52s ``` Svelte check: ```text svelte-check found 0 errors and 4 warnings in 3 files ``` The full report, renderer boundary review, migration upgrade evidence, exact gate excerpts and decisions are in `docs/audits/merge-round-7c.md` and the final #867 comment. Staging blockers include the Notes process SIGSEGV after 278 passing assertions (#1069), stale performance exception pins, the retained thumbnail test conflict, Sketch save and unfinished verification. No pushes or deployments.
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#989
No description provided.