CANVAS: Share and Collaborate — live co-editing with presence, view-only links (§60, headline) #991

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

Owner direction (2026-10-03, #509)

"Sharing/collab would be the most awesome feature, because Excalidraw's hosted product doesn't allow self-hosting, and this adds state to Excalidraw, the biggest missing part." Contract: DESIGN §60 "Share and Collaborate", §54 (Share vs Collaborate, public links view only), §61 history. Builds on #976.

Scope

  • Collaborate on a canvas (invite a User or group): live co-editing with presence (named cursors, colours, selection outlines, "who is here" avatars in the one bar), follow-a-collaborator, live laser pointer. All edits go through the one collaboration event path; each edit is attributed (§61) and undoable per author.
  • Share and public links: view only (§54), live-updating view, pan/zoom, element deep links work; no editing affordances, no outgoing requests from viewers.
  • Offline/reconnect: edits made while disconnected merge on reconnect; clear status in plain words (no jargon).
  • Live cards (#977) on a shared canvas resolve per viewer; placeholders leak nothing.
  • Polish: presence motion uses the shared spring; reduced motion respected; keyboard and screen reader announce joins/leaves politely.
  • Security: per-update authz (Viewer updates disconnect), author bound to connection, size/rate limits (see #976 security comment); classify routes in the #472 matrix; adversarial cases for a revoked collaborator mid-session.
  • Bench: 10 collaborators drawing at once on a 5k-element canvas: p95 update latency, server CPU/RSS.

Verification

Two- and three-browser e2e as Users (owner + collaborator + viewer): live strokes appear, presence shows, viewer cannot edit, revoke kicks the collaborator within the recheck window. Mac + iPad simulator screenshots.

## Owner direction (2026-10-03, #509) "Sharing/collab would be the most awesome feature, because Excalidraw's hosted product doesn't allow self-hosting, and this adds state to Excalidraw, the biggest missing part." Contract: DESIGN §60 "Share and Collaborate", §54 (Share vs Collaborate, public links view only), §61 history. Builds on #976. ## Scope - **Collaborate** on a canvas (invite a User or group): live co-editing with presence (named cursors, colours, selection outlines, "who is here" avatars in the one bar), follow-a-collaborator, live laser pointer. All edits go through the one collaboration event path; each edit is attributed (§61) and undoable per author. - **Share** and **public links**: view only (§54), live-updating view, pan/zoom, element deep links work; no editing affordances, no outgoing requests from viewers. - Offline/reconnect: edits made while disconnected merge on reconnect; clear status in plain words (no jargon). - Live cards (#977) on a shared canvas resolve per viewer; placeholders leak nothing. - Polish: presence motion uses the shared spring; reduced motion respected; keyboard and screen reader announce joins/leaves politely. - Security: per-update authz (Viewer updates disconnect), author bound to connection, size/rate limits (see #976 security comment); classify routes in the #472 matrix; adversarial cases for a revoked collaborator mid-session. - Bench: 10 collaborators drawing at once on a 5k-element canvas: p95 update latency, server CPU/RSS. ## Verification Two- and three-browser e2e as Users (owner + collaborator + viewer): live strokes appear, presence shows, viewer cannot edit, revoke kicks the collaborator within the recheck window. Mac + iPad simulator screenshots.
Author
Owner

Started #991 on job/canvas-collab-991, Canvas core base beb3bbf4b. Merged origin/dev; start HEAD is a2a95af2b0.

Findings: Canvas core already validates author-bound events and rechecks recipient access for every frame/broadcast. The current web editor disables offline drawing; awareness renders cursors only; shared Canvas files fall back to Quick Look because the own-Home Note index cannot resolve recipient paths. Public Canvas collaboration sockets currently return 404. §61 requires a measured history encoding decision; this job must not choose one without that decision.

Work starts with reconnect-safe event retention and presence on the existing Canvas event path. No new dependencies are planned. Full multi-browser, adversarial, Mac interoperability and performance runs are reserved for the merge round under the latest verification policy.

Started #991 on `job/canvas-collab-991`, Canvas core base `beb3bbf4b`. Merged `origin/dev`; start HEAD is a2a95af2b0afad839731aa14285cf6e0b58d6a0f. Findings: Canvas core already validates author-bound events and rechecks recipient access for every frame/broadcast. The current web editor disables offline drawing; awareness renders cursors only; shared Canvas files fall back to Quick Look because the own-Home Note index cannot resolve recipient paths. Public Canvas collaboration sockets currently return 404. §61 requires a measured history encoding decision; this job must not choose one without that decision. Work starts with reconnect-safe event retention and presence on the existing Canvas event path. No new dependencies are planned. Full multi-browser, adversarial, Mac interoperability and performance runs are reserved for the merge round under the latest verification policy.
Author
Owner

Started #991 on job/canvas-collab-991, Canvas core base beb3bbf4b. Merged origin/dev; start HEAD is a2a95af2b0.

Findings: Canvas core already validates author-bound events and rechecks recipient access for every frame/broadcast. The current web editor disables offline drawing; awareness renders cursors only; shared Canvas files fall back to Quick Look because the own-Home Note index cannot resolve recipient paths. Public Canvas collaboration sockets currently return 404. §61 requires a measured history encoding decision; this job must not choose one without that decision.

Work starts with reconnect-safe event retention and presence on the existing Canvas event path. No new dependencies are planned. Full multi-browser, adversarial, Mac interoperability and performance runs are reserved for the merge round under the latest verification policy.

Started #991 on `job/canvas-collab-991`, Canvas core base `beb3bbf4b`. Merged `origin/dev`; start HEAD is a2a95af2b0afad839731aa14285cf6e0b58d6a0f. Findings: Canvas core already validates author-bound events and rechecks recipient access for every frame/broadcast. The current web editor disables offline drawing; awareness renders cursors only; shared Canvas files fall back to Quick Look because the own-Home Note index cannot resolve recipient paths. Public Canvas collaboration sockets currently return 404. §61 requires a measured history encoding decision; this job must not choose one without that decision. Work starts with reconnect-safe event retention and presence on the existing Canvas event path. No new dependencies are planned. Full multi-browser, adversarial, Mac interoperability and performance runs are reserved for the merge round under the latest verification policy.
Author
Owner

Implemented and committed acknowledged Canvas events (3dd2ef079) and recipient streams with live access and presence (281b527ae).

Verified finding: the room baseline contains the drawing body without frontmatter. Recipient titles must come from the Notes index. The real-socket regression caught this and now passes: one collaborator event reaches a Viewer, downgrade sends editable: false, revoke closes with code 1008, and Viewer edits do not persist.

The web uses one User-scoped presence list, shared cursor springs, selection outlines, laser state, follow controls and an in-memory event outbox. A local recovery download preserves unsaved drawing after a lost grant or room epoch. Focused Vitest: 35 passed. Web check: svelte-check found 0 errors and 0 warnings. Production screenshot capture is in progress.

Decisions: use a server session frame (103) on the existing socket to report access and validated recipient source; use /n/<Note ID>?owner=<User ID> for recipient navigation; cap the acknowledged outbox at 2 MiB, and budget two presence plus two drawing frames per second. No dependency or migration was added.

Remaining scope: public Canvas rendering/updates, group recipients, live-card viewer resolution (#977), durable per-author history (§61/#975), and plain-JSON Canvas discovery from Shared. These prevent READY FOR MERGE until completed. Full matrices, Apple interoperability and performance measurements remain merge-round work per the latest verification policy.

Implemented and committed acknowledged Canvas events (`3dd2ef079`) and recipient streams with live access and presence (`281b527ae`). Verified finding: the room baseline contains the drawing body without frontmatter. Recipient titles must come from the Notes index. The real-socket regression caught this and now passes: one collaborator event reaches a Viewer, downgrade sends `editable: false`, revoke closes with code 1008, and Viewer edits do not persist. The web uses one User-scoped presence list, shared cursor springs, selection outlines, laser state, follow controls and an in-memory event outbox. A local recovery download preserves unsaved drawing after a lost grant or room epoch. Focused Vitest: 35 passed. Web check: `svelte-check found 0 errors and 0 warnings`. Production screenshot capture is in progress. Decisions: use a server session frame (103) on the existing socket to report access and validated recipient source; use `/n/<Note ID>?owner=<User ID>` for recipient navigation; cap the acknowledged outbox at 2 MiB, and budget two presence plus two drawing frames per second. No dependency or migration was added. Remaining scope: public Canvas rendering/updates, group recipients, live-card viewer resolution (#977), durable per-author history (§61/#975), and plain-JSON Canvas discovery from Shared. These prevent READY FOR MERGE until completed. Full matrices, Apple interoperability and performance measurements remain merge-round work per the latest verification policy.
Author
Owner

Production evidence found a presence colour mismatch: Excalidraw 0.18's getClientColor hashes the collaborator ID and ignores Collaborator.color. Canvas avatars used the Notes palette, so Ari's avatar and cursor had different colours. The Canvas boundary now uses the renderer's identity hash for its Top row projection. Notes colour behaviour is unchanged. The same User keeps the same Canvas colour across reconnects and device widths. Focused web gates and refreshed production evidence are running.

Production evidence found a presence colour mismatch: Excalidraw 0.18's getClientColor hashes the collaborator ID and ignores Collaborator.color. Canvas avatars used the Notes palette, so Ari's avatar and cursor had different colours. The Canvas boundary now uses the renderer's identity hash for its Top row projection. Notes colour behaviour is unchanged. The same User keeps the same Canvas colour across reconnects and device widths. Focused web gates and refreshed production evidence are running.
Author
Owner

READY FOR MERGE: no

Head: 6964681d783ee856ad123cbf1e4c696778bea7cf. Branch: job/canvas-collab-991. Base: approved Canvas core beb3bbf4b. The initial origin/dev merge is a2a95af2b; the one final fetch/merge reported Already up to date. No push or deploy was run. #991 stays open.

Built

Authenticated Canvas collaboration uses the core event validator, author identity and single room writer. Collaborate recipients open a live Canvas by immutable owner and Note IDs. Share recipients see the same scene without edit controls. The server reports access on the existing socket, removes edit access after downgrade and closes revoked Canvas connections with 1008. A three-socket regression checks scene fan-out, downgrade, revoke and refused Viewer edits.

Presence includes named cursors, selection outlines, laser state, one avatar row and follow. Remote cursors use the shared spring and reduced-motion setting. Escape stops follow from the avatar button. Canvas avatars match the renderer's identity-derived cursor colour. Presence contains no remote avatar or embed URL.

Drawing edits stay in an in-memory outbox until the server echoes them. Reconnect sync runs before replay. Large edits drain as whole-element frames within the 2 MiB budget. Remote updates retain pending local edits. A local download preserves unsaved drawing after access loss or a room epoch change.

The existing Share dialog opens from Canvas. Recipient Copy link keeps owner, Note and element IDs. Shared Canvas Markdown opens from Files using a bounded frontmatter read. A ten-User benchmark profile covers 100 and 5,000 elements, authoritative echo p50/p95, a burst and server CPU/RSS.

Files

  • apps/web/src/lib/canvas/CanvasReact.tsx, CanvasView.svelte, canvas.css, scene.ts, scene.test.ts
  • apps/web/src/lib/notes/collab.ts, collab.test.ts
  • apps/web/src/lib/files/FilesBrowser.svelte
  • apps/web/src/routes/notes/[id]/+page.svelte
  • crates/calternal-collab/src/session.rs, tests/canvas_shares.rs, README.md
  • apps/web/e2e/canvas-collab-991.mjs
  • bench/canvas-collab-991.mjs

Five atomic implementation/test commits follow the initial merge: 3dd2ef079, 281b527ae, 5e6139a96, cb8dea270, 6964681d7. No dependency or migration was added. Doc comments were read before this report.

UX gaps closed

Shared Canvas files no longer fall back to Quick Look when their Markdown frontmatter has a stable ID. Share viewers cannot enter Edit. Downgrade removes Edit; revoke stops reconnect retries. Pending drawings survive connection loss in memory and offer a local recovery download. Escape works while follow controls own focus. Upstream duplicate avatars are hidden. Avatar, cursor and selection colours use the same identity. Large edits do not wait forever on an oversized aggregate event.

Known gaps / UX gaps left

This is an authenticated collaboration slice, not all of #991. Public Canvas rendering and public live updates remain absent. Group recipients require Files Share support. Viewer-specific live cards remain #977 work. Durable per-author history and undo await the measured §61/#975 encoding decision; current source Versions are not that history. Plain JSON .excalidraw discovery from Shared remains absent. Recipient PNG/SVG export is absent. The outbox is not persisted across browser closure; a single element above the core event limit stays unsaved and can be downloaded. Real iPad/Pencil, real macOS, offline multi-browser conflict scenarios and follow camera motion still need merge-round review.

Decisions

The socket uses session frame 103 for server-confirmed access and the validated recipient source. Recipient navigation uses /n/<Note ID>?owner=<User ID>; the owner hint is not authorization. The outbox is bounded at 2 MiB and stays in memory. Drawing and presence each use at most two frames per second. Follow uses the peer's viewport centre and zoom; local gestures or Escape stop it. Excalidraw 0.18 ignores its colour field, so the Canvas boundary mirrors its small identity hash for avatar colour. Notes colour behaviour is unchanged. No history encoding was selected without §61's measurement decision.

Verification

Rust gates used CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4 and the preset job target. cargo fmt --check exited 0 with no output. The per-crate clippy and test commands passed. The full collab suite preceded the recipient title fix; the new focused socket regression passed after that fix. Full gate logs stay in artifacts/.

Verbatim retained output follows.

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

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

cargo test -p calternal-collab

test result: ok. 44 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 13.45s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.79s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.74s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 60.01s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.72s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.70s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.57s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.26s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.80s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 36.99s
test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.15s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

cargo test -p calternal-collab --test canvas_shares -- --test-threads=2

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

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

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 05s

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

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

bun run check

svelte-check found 0 errors and 0 warnings

bunx vitest run src/lib/notes/collab.test.ts src/lib/canvas/scene.test.ts --maxWorkers=2

 Test Files  2 passed (2)
      Tests  37 passed (37)

production focused browser regression

Canvas #991 production regression passed: three Users, strokes, viewer, follow, downgrade, revoke; 30 macOS screenshots.

bun run build exited 0. Existing bundle/import.meta warnings remain. Script syntax checks and git diff --check passed. Performance was not measured: the latest branch verification policy reserves measurements for performance issues. docs/perf/baseline.json has no ten-User Canvas metric, so no performance comparison is claimed.

For the merge round

Run with its prepared server and authorization fixture environment:

  • cd apps/web && bun run test -- --maxWorkers=2: full web regression suite.
  • bun apps/web/e2e/canvas-976.mjs and bun apps/web/e2e/share.mjs: core and Share cross-feature regression.
  • bun apps/web/e2e/canvas-collab-991.mjs: repeat this focused production regression against the combined server.
  • bash tests/adversarial/run-split.sh: full authorization, cross-User and robustness matrices, including room isolation. No full hostile-input matrix was run in this branch job.
  • On the perf VM, with CALTERNAL_SERVER_BIN pointing at the shared release server: flock /root/perf.lock bash -c 'uptime; bun bench/canvas-collab-991.mjs --json artifacts/canvas-collab-profile.json'. Establish ten-User baseline numbers and confirm burst behaviour. Never compile there.
  • Under the Mac VM lock, check the combined build with real macOS/iPad clients: three concurrent drawings, follow/laser, Pencil/touch, disconnect/reconnect and revoke. This job captured macOS-emulated Chromium; it does not claim real Apple-client evidence.

Production screenshots

Thirty macOS-emulated production screenshots cover owner, Collaborate recipient, Share recipient, Canvas menu and the existing Share dialog at 390, 820 and 1440 px in light and dark. Claude/orchestrator owns the visual quality review. The earlier attachment set preceded the colour correction; use the following refreshed set.

Screen 390 light 390 dark 820 light 820 dark 1440 light 1440 dark
owner PNG PNG PNG PNG PNG PNG
collaborator PNG PNG PNG PNG PNG PNG
viewer PNG PNG PNG PNG PNG PNG
menu PNG PNG PNG PNG PNG PNG
share PNG PNG PNG PNG PNG PNG

Cleanup completed. Web build output was deleted. Working tree is clean. cargo clean output:

     Removed 15935 files, 10.5GiB total
READY FOR MERGE: no Head: `6964681d783ee856ad123cbf1e4c696778bea7cf`. Branch: `job/canvas-collab-991`. Base: approved Canvas core `beb3bbf4b`. The initial origin/dev merge is `a2a95af2b`; the one final fetch/merge reported Already up to date. No push or deploy was run. #991 stays open. ## Built Authenticated Canvas collaboration uses the core event validator, author identity and single room writer. Collaborate recipients open a live Canvas by immutable owner and Note IDs. Share recipients see the same scene without edit controls. The server reports access on the existing socket, removes edit access after downgrade and closes revoked Canvas connections with 1008. A three-socket regression checks scene fan-out, downgrade, revoke and refused Viewer edits. Presence includes named cursors, selection outlines, laser state, one avatar row and follow. Remote cursors use the shared spring and reduced-motion setting. Escape stops follow from the avatar button. Canvas avatars match the renderer's identity-derived cursor colour. Presence contains no remote avatar or embed URL. Drawing edits stay in an in-memory outbox until the server echoes them. Reconnect sync runs before replay. Large edits drain as whole-element frames within the 2 MiB budget. Remote updates retain pending local edits. A local download preserves unsaved drawing after access loss or a room epoch change. The existing Share dialog opens from Canvas. Recipient Copy link keeps owner, Note and element IDs. Shared Canvas Markdown opens from Files using a bounded frontmatter read. A ten-User benchmark profile covers 100 and 5,000 elements, authoritative echo p50/p95, a burst and server CPU/RSS. ## Files - `apps/web/src/lib/canvas/CanvasReact.tsx`, `CanvasView.svelte`, `canvas.css`, `scene.ts`, `scene.test.ts` - `apps/web/src/lib/notes/collab.ts`, `collab.test.ts` - `apps/web/src/lib/files/FilesBrowser.svelte` - `apps/web/src/routes/notes/[id]/+page.svelte` - `crates/calternal-collab/src/session.rs`, `tests/canvas_shares.rs`, `README.md` - `apps/web/e2e/canvas-collab-991.mjs` - `bench/canvas-collab-991.mjs` Five atomic implementation/test commits follow the initial merge: `3dd2ef079`, `281b527ae`, `5e6139a96`, `cb8dea270`, `6964681d7`. No dependency or migration was added. Doc comments were read before this report. ## UX gaps closed Shared Canvas files no longer fall back to Quick Look when their Markdown frontmatter has a stable ID. Share viewers cannot enter Edit. Downgrade removes Edit; revoke stops reconnect retries. Pending drawings survive connection loss in memory and offer a local recovery download. Escape works while follow controls own focus. Upstream duplicate avatars are hidden. Avatar, cursor and selection colours use the same identity. Large edits do not wait forever on an oversized aggregate event. ## Known gaps / UX gaps left This is an authenticated collaboration slice, not all of #991. Public Canvas rendering and public live updates remain absent. Group recipients require Files Share support. Viewer-specific live cards remain #977 work. Durable per-author history and undo await the measured §61/#975 encoding decision; current source Versions are not that history. Plain JSON `.excalidraw` discovery from Shared remains absent. Recipient PNG/SVG export is absent. The outbox is not persisted across browser closure; a single element above the core event limit stays unsaved and can be downloaded. Real iPad/Pencil, real macOS, offline multi-browser conflict scenarios and follow camera motion still need merge-round review. ## Decisions The socket uses session frame 103 for server-confirmed access and the validated recipient source. Recipient navigation uses `/n/<Note ID>?owner=<User ID>`; the owner hint is not authorization. The outbox is bounded at 2 MiB and stays in memory. Drawing and presence each use at most two frames per second. Follow uses the peer's viewport centre and zoom; local gestures or Escape stop it. Excalidraw 0.18 ignores its colour field, so the Canvas boundary mirrors its small identity hash for avatar colour. Notes colour behaviour is unchanged. No history encoding was selected without §61's measurement decision. ## Verification Rust gates used `CARGO_PROFILE_DEV_DEBUG=line-tables-only CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=4` and the preset job target. `cargo fmt --check` exited 0 with no output. The per-crate clippy and test commands passed. The full collab suite preceded the recipient title fix; the new focused socket regression passed after that fix. Full gate logs stay in `artifacts/`. Verbatim retained output follows. `cargo clippy -p calternal-collab --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.41s ``` `cargo test -p calternal-collab` ```text test result: ok. 44 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 13.45s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.79s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.74s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 60.01s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.72s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.70s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.57s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 11.26s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.80s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 36.99s test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 29.15s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` `cargo test -p calternal-collab --test canvas_shares -- --test-threads=2` ```text test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.57s ``` `cargo clippy -p calternal-server --all-targets -- -D warnings` ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 2m 05s ``` `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 19.55s ``` `bun run check` ```text svelte-check found 0 errors and 0 warnings ``` `bunx vitest run src/lib/notes/collab.test.ts src/lib/canvas/scene.test.ts --maxWorkers=2` ```text Test Files 2 passed (2) Tests 37 passed (37) ``` `production focused browser regression` ```text Canvas #991 production regression passed: three Users, strokes, viewer, follow, downgrade, revoke; 30 macOS screenshots. ``` `bun run build` exited 0. Existing bundle/import.meta warnings remain. Script syntax checks and `git diff --check` passed. Performance was not measured: the latest branch verification policy reserves measurements for performance issues. `docs/perf/baseline.json` has no ten-User Canvas metric, so no performance comparison is claimed. ## For the merge round Run with its prepared server and authorization fixture environment: - `cd apps/web && bun run test -- --maxWorkers=2`: full web regression suite. - `bun apps/web/e2e/canvas-976.mjs` and `bun apps/web/e2e/share.mjs`: core and Share cross-feature regression. - `bun apps/web/e2e/canvas-collab-991.mjs`: repeat this focused production regression against the combined server. - `bash tests/adversarial/run-split.sh`: full authorization, cross-User and robustness matrices, including room isolation. No full hostile-input matrix was run in this branch job. - On the perf VM, with CALTERNAL_SERVER_BIN pointing at the shared release server: `flock /root/perf.lock bash -c 'uptime; bun bench/canvas-collab-991.mjs --json artifacts/canvas-collab-profile.json'`. Establish ten-User baseline numbers and confirm burst behaviour. Never compile there. - Under the Mac VM lock, check the combined build with real macOS/iPad clients: three concurrent drawings, follow/laser, Pencil/touch, disconnect/reconnect and revoke. This job captured macOS-emulated Chromium; it does not claim real Apple-client evidence. ## Production screenshots Thirty macOS-emulated production screenshots cover owner, Collaborate recipient, Share recipient, Canvas menu and the existing Share dialog at 390, 820 and 1440 px in light and dark. Claude/orchestrator owns the visual quality review. The earlier attachment set preceded the colour correction; use the following refreshed set. | Screen | 390 light | 390 dark | 820 light | 820 dark | 1440 light | 1440 dark | |---|---|---|---|---|---|---| | owner | [PNG](https://git.kayg.org/attachments/3d1878bf-47dd-4a24-8170-27288833fb84) | [PNG](https://git.kayg.org/attachments/dbc26458-5706-49a6-867f-032b45f608a3) | [PNG](https://git.kayg.org/attachments/c6ed9958-55ae-4fbe-bdab-5a01e08b044d) | [PNG](https://git.kayg.org/attachments/2f5aea73-bc61-422c-b809-ad60b8c6d169) | [PNG](https://git.kayg.org/attachments/ba9f132e-1d1b-4975-9f4b-231d8a74256a) | [PNG](https://git.kayg.org/attachments/054e9394-7357-4a22-b49a-08105f468107) | | collaborator | [PNG](https://git.kayg.org/attachments/106d4938-8979-4941-a78f-604e8ba8f127) | [PNG](https://git.kayg.org/attachments/b221b87a-3f31-4334-980b-31a1d582d1b9) | [PNG](https://git.kayg.org/attachments/a14d4d12-f5a5-45e3-b8ac-555bae451766) | [PNG](https://git.kayg.org/attachments/a13fc3bd-ae93-4643-a4a4-d3023c18b29f) | [PNG](https://git.kayg.org/attachments/de575471-2ab1-444a-8ad3-2a53108c7784) | [PNG](https://git.kayg.org/attachments/663a586d-0dfc-4d0a-bb12-473a9645e073) | | viewer | [PNG](https://git.kayg.org/attachments/3adc15b9-f23e-424f-89cb-e9f2133bfa80) | [PNG](https://git.kayg.org/attachments/c677ba73-0250-4df7-ba65-cb1b5e7ee284) | [PNG](https://git.kayg.org/attachments/a795efd6-52eb-4289-bd45-736065c0bbd7) | [PNG](https://git.kayg.org/attachments/db59931d-61ca-41d4-8657-3437e62cc89d) | [PNG](https://git.kayg.org/attachments/e5558aa3-94ca-41bf-a3ff-0ee1b74fbc97) | [PNG](https://git.kayg.org/attachments/d69271c2-f817-458f-ab48-465391dbd5ac) | | menu | [PNG](https://git.kayg.org/attachments/8a3c1b43-18be-447f-bc1d-48eea29074b8) | [PNG](https://git.kayg.org/attachments/e2007f21-dacd-4cc9-96de-b132104f4eb6) | [PNG](https://git.kayg.org/attachments/00d5b992-dc56-46d1-91dd-a1a6cde524a1) | [PNG](https://git.kayg.org/attachments/1ed086e2-0c0c-479c-9511-44c713426a21) | [PNG](https://git.kayg.org/attachments/c08ee8d6-61e7-4864-b6c2-f1805b62ad2a) | [PNG](https://git.kayg.org/attachments/9e3b202c-c0ae-43fd-a321-d863c59edc43) | | share | [PNG](https://git.kayg.org/attachments/9bf5cc92-386f-4cae-b6b9-3b69fc86d86c) | [PNG](https://git.kayg.org/attachments/5e829ee0-9d2c-4f4f-bc34-0108186539cd) | [PNG](https://git.kayg.org/attachments/986c651e-ec15-4aa6-b38a-3f0d19c34ed8) | [PNG](https://git.kayg.org/attachments/f0d79d1d-a9c5-4f2f-b59a-6f32f183b59f) | [PNG](https://git.kayg.org/attachments/a0575825-8974-4912-826f-b5c85eb2f875) | [PNG](https://git.kayg.org/attachments/4245a487-266e-4550-baa1-71021ffa870b) | Cleanup completed. Web build output was deleted. Working tree is clean. `cargo clean` output: ```text Removed 15935 files, 10.5GiB total ```
Author
Owner

Continuing round 2 on job/canvas-collab-991, base/head 6964681d7. Remaining work: public Canvas views, Group sharing, Shared discovery, recipient exports.

Finding: the repository has no instance Group or Group membership security-state tables/API (only photo grouping and OIDC role mapping). Group sharing needs the instance Group primitive; I will leave that gap explicit rather than create a Canvas-only Group authority that changes another crate’s design.

Decision: use the existing public-link authorization for a bounded, read-only Canvas projection, with private references replaced on the server before returning JSON. Live cards and history remain #977/#975. Full adversarial matrices and full e2e belong to the merge round under the current verification policy.

Continuing round 2 on `job/canvas-collab-991`, base/head `6964681d7`. Remaining work: public Canvas views, Group sharing, Shared discovery, recipient exports. Finding: the repository has no instance Group or Group membership security-state tables/API (only photo grouping and OIDC role mapping). Group sharing needs the instance Group primitive; I will leave that gap explicit rather than create a Canvas-only Group authority that changes another crate’s design. Decision: use the existing public-link authorization for a bounded, read-only Canvas projection, with private references replaced on the server before returning JSON. Live cards and history remain #977/#975. Full adversarial matrices and full e2e belong to the merge round under the current verification policy.
Author
Owner

Round 2 findings and progress:

  • Public byte routes exposed raw Canvas JSON, including private card metadata. The Canvas view, Preview and Download now use one server projection. Public thumbnails cannot read raw Canvas JSON. A focused Files regression passed before the final gates; it now also covers both formats and rejects Canvas public Edit/Linked notes grants.
  • Shared navigation scanned only a frontmatter prefix. Plain .excalidraw files have no frontmatter. Authorized listings now include the indexed Canvas Note ID; the browser uses it directly.
  • Viewer export previously required an owner-only save action. Recipient export now uses the existing renderer, flushes the current room without submitting an edit, and rechecks the Share after rendering. App font follows the recipient’s settings.
  • Notes core gates passed (538 unit tests plus integration vectors). Notes plugin gates passed (192 tests, one existing ignored test). Focused Vitest: 5 files / 51 tests passed. Final Files/Collab/Server gates and production screenshot regression are in progress.

Commits so far: a898c4f70, 24b1982f9, f86a35c3e. One required merge of origin/dev completed (843af4f05). Group sharing still needs the missing Instance Group authority; no Canvas-only Group store was introduced. Public reads use 2-second serial conditional requests and stop in hidden pages; the public hot path was added to the existing #991 bench profile. Measurements and full matrices remain merge-round work under the current verification policy.

Round 2 findings and progress: - Public byte routes exposed raw Canvas JSON, including private card metadata. The Canvas view, Preview and Download now use one server projection. Public thumbnails cannot read raw Canvas JSON. A focused Files regression passed before the final gates; it now also covers both formats and rejects Canvas public Edit/Linked notes grants. - Shared navigation scanned only a frontmatter prefix. Plain `.excalidraw` files have no frontmatter. Authorized listings now include the indexed Canvas Note ID; the browser uses it directly. - Viewer export previously required an owner-only save action. Recipient export now uses the existing renderer, flushes the current room without submitting an edit, and rechecks the Share after rendering. App font follows the recipient’s settings. - Notes core gates passed (538 unit tests plus integration vectors). Notes plugin gates passed (192 tests, one existing ignored test). Focused Vitest: 5 files / 51 tests passed. Final Files/Collab/Server gates and production screenshot regression are in progress. Commits so far: `a898c4f70`, `24b1982f9`, `f86a35c3e`. One required merge of `origin/dev` completed (`843af4f05`). Group sharing still needs the missing Instance Group authority; no Canvas-only Group store was introduced. Public reads use 2-second serial conditional requests and stop in hidden pages; the public hot path was added to the existing #991 bench profile. Measurements and full matrices remain merge-round work under the current verification policy.
Author
Owner

READY FOR MERGE: no

Head: b17f3f038523bb710fcd5464da2a5640f94f290f on job/canvas-collab-991.

Round 2 builds public Canvas views, Shared JSON discovery and recipient exports. Group sharing remains unfinished because there is no Instance Group or membership authority in this repository. A Canvas-only Group store would change Security state design outside this job.

Built

  • Public view-only Canvas links use a live, passive scene projection with pan/zoom and stable element links. Serial conditional reads run every two seconds and stop when hidden. The public host has no collaboration provider, write callback, asset resolver or outgoing-link handler.
  • Public View, Preview and Download use the same projection. Private cards and image references become neutral geometry placeholders. Unknown fields, custom data, links and binary assets are removed. Public Edit, linked Notes and raw thumbnails are refused for Canvas files, including legacy grants.
  • Shared listings return the authorized indexed Canvas Note identity for both file formats. Plain JSON opens as a Canvas, including the existing stable file Copy link alias.
  • Recipients export PNG/SVG through the normal Canvas menu and existing bounded renderer. Export flushes the live room without a recipient write, then checks the Share again after rendering. The app font follows recipient settings.
  • Generated OpenAPI, action registry, API types and cross-user classification include both new routes. The existing benchmark now covers conditional public reads and eight-viewer bursts at 100/5,000 elements.

Files

  • crates/calternal-notes-core/src/canvas.rs
  • crates/plugins/notes/src/{canvas_export.rs,lib.rs}
  • crates/calternal-collab/src/session.rs; crates/calternal-collab/tests/canvas_shares.rs
  • crates/plugins/files/src/{public.rs,listing.rs,shares.rs,lib.rs}
  • apps/web/src/lib/canvas/{CanvasReact.tsx,CanvasView.svelte,PublicCanvasView.svelte}
  • apps/web/src/lib/files/{FilesBrowser.svelte,PublicLinkPage.svelte,ShareDialog.svelte,model.ts,model.test.ts}
  • apps/web/e2e/canvas-collab-991.mjs; bench/canvas-collab-991.mjs
  • contracts/{openapi.json,actions.json,action-overrides.json}; packages/api-client/src/generated.ts
  • tests/adversarial/xuser_matrix.py

UX gaps closed

  • A Viewer can export without submitting an empty edit event.
  • Shared plain JSON and its file Copy link open the drawing instead of a JSON Quick Look.
  • Public raw-byte routes cannot expose private card metadata.
  • Canvas public-link options no longer offer Edit or linked Notes. New Canvas links default to a 30-day expiry.
  • Public zoom controls reuse warm tooltips with shortcut hints. Element links use the shared viewport settling duration for pointer and keyboard.
  • Public drawing revisions retain the visitor viewport. Access removal releases rendered pixels; offline reads keep the last received scene and retry.

UX gaps left / known gaps

  • Collaborate with a Group needs the Instance Group and membership authority. No Group UI or independent Canvas membership store was added.
  • #977 owns live-card resolution. Until its per-viewer resolver is connected, public scenes and recipient exports conservatively replace all embedded/image references with placeholders. Owner exports remain lossless. #975 owns history.
  • Visual approval belongs to Claude/the orchestrator. This job attaches real production screenshots and does not approve their visual quality.

Decisions

  • Use two-second serial conditional GETs for public committed revisions instead of public editing-room connections. Pause in hidden pages.
  • Preserve element IDs and geometry in private placeholders; strip opaque values even under otherwise familiar field names.
  • Use the same conservative projection for recipient exports until #977 supplies per-viewer reference resolution.
  • Retry one 404 only for an already-rendered public scene to cover the brief atomic Note-save/Files-index interval. Each retry checks current authority; subsequent denial removes pixels. Password/access denials are immediate.
  • Do not create a new Group authority in this feature. The required origin/dev merge completed once at 843af4f05. No dependency or migration was added.

Verification (gate output verbatim)
cargo fmt --check exited 0 with no output.

core:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.67s
test result: ok. 538 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.09s
test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.11s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s
test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.59s
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

notes:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.84s
test result: ok. 192 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 200.40s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.89s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

files:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.14s
test result: ok. 162 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 115.93s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

collab:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 20.36s
test result: ok. 44 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.00s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.32s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.81s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.24s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 39.90s
test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.21s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.96s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.19s
test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.55s
test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.84s
test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 19.04s
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 19.80s
test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

server:

    Finished `dev` profile [unoptimized + debuginfo] target(s) in 23.24s
test result: ok. 162 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 14.95s

Web and registry:

svelte-check found 0 errors and 0 warnings
 Test Files  5 passed (5)
      Tests  51 passed (51)
Action registry: 344 operations, 326 generated tools
Cross-User classification gate: 346 operations classified
Generated entry point classification: 978 tools classified
Ran 12 tests in 0.184s
OK

The server suite initially found a missing 429 declaration on the new public route. The declaration was fixed; the focused regression and final server suite passed. New browser assertions were corrected for canonical /notes routing, shortened filenames and anonymous OS theme behavior. Existing test expectations were not changed.

For the merge round

  • bun run --cwd apps/web test --maxWorkers=2: full web regression coverage.
  • bash tests/adversarial/run-split.sh: authz, cross-user and robustness matrices, including new export and public-read classification rows. Focused live regression covers public POST refusal and mid-session Share/public-link revocation in this branch.
  • bun apps/web/e2e/canvas-976.mjs and bun apps/web/e2e/canvas-export-976.mjs: full owner drawing/export behavior with the combined branches.
  • Real macOS checks under flock ~/.local/state/codex-jobs/calternal/macvm.lock, as required by the shared verification policy. No Mac GUI session or staging deployment ran in this job.
  • Performance review only: on the perf VM with the shared release server configured, flock /root/perf.lock bash -c 'cat /proc/loadavg; bun bench/canvas-collab-991.mjs --json artifacts/canvas-collab-profile.json'. No measurements ran here under the current policy. There is no public Canvas projection baseline in docs/perf/baseline.json; numbers remain unmeasured.

Focused production browser output (verbatim)

Canvas test: three Users registered.
Canvas test: collaborator and viewer opened the real scene.
Canvas test: anonymous pan/zoom, private placeholders and element links passed.
Canvas test: recipient PNG/SVG exports passed.
Canvas #991 production regression passed: three Users plus public viewer, strokes, exports, Shared JSON discovery, follow, downgrade, revoke; 48 macOS screenshots.

Production build exited 0. Type checks passed after the viewport-motion and shared-tooltip changes. The screenshot set is from that final build. Screenshot-only settling waits do not delay product content or input. Icon/label pairs were inspected at original screenshot resolution; visual approval remains with the orchestrator.

Cleanup (verbatim)

     Removed 19278 files, 13.1GiB total

Generated web build, renderer and font output was removed. git status --short has no output. No push or deployment ran.

Evidence

All 48 macOS production screenshots and gate logs

Screen 390 light 390 dark 820 light 820 dark 1440 light 1440 dark
public PNG PNG PNG PNG PNG PNG
shared PNG PNG PNG PNG PNG PNG
owner PNG PNG PNG PNG PNG PNG
collaborator PNG PNG PNG PNG PNG PNG
viewer PNG PNG PNG PNG PNG PNG
menu PNG PNG PNG PNG PNG PNG
share PNG PNG PNG PNG PNG PNG
share-links PNG PNG PNG PNG PNG PNG
READY FOR MERGE: no Head: `b17f3f038523bb710fcd5464da2a5640f94f290f` on `job/canvas-collab-991`. Round 2 builds public Canvas views, Shared JSON discovery and recipient exports. Group sharing remains unfinished because there is no Instance Group or membership authority in this repository. A Canvas-only Group store would change Security state design outside this job. Built - Public view-only Canvas links use a live, passive scene projection with pan/zoom and stable element links. Serial conditional reads run every two seconds and stop when hidden. The public host has no collaboration provider, write callback, asset resolver or outgoing-link handler. - Public View, Preview and Download use the same projection. Private cards and image references become neutral geometry placeholders. Unknown fields, custom data, links and binary assets are removed. Public Edit, linked Notes and raw thumbnails are refused for Canvas files, including legacy grants. - Shared listings return the authorized indexed Canvas Note identity for both file formats. Plain JSON opens as a Canvas, including the existing stable file Copy link alias. - Recipients export PNG/SVG through the normal Canvas menu and existing bounded renderer. Export flushes the live room without a recipient write, then checks the Share again after rendering. The app font follows recipient settings. - Generated OpenAPI, action registry, API types and cross-user classification include both new routes. The existing benchmark now covers conditional public reads and eight-viewer bursts at 100/5,000 elements. Files - crates/calternal-notes-core/src/canvas.rs - crates/plugins/notes/src/{canvas_export.rs,lib.rs} - crates/calternal-collab/src/session.rs; crates/calternal-collab/tests/canvas_shares.rs - crates/plugins/files/src/{public.rs,listing.rs,shares.rs,lib.rs} - apps/web/src/lib/canvas/{CanvasReact.tsx,CanvasView.svelte,PublicCanvasView.svelte} - apps/web/src/lib/files/{FilesBrowser.svelte,PublicLinkPage.svelte,ShareDialog.svelte,model.ts,model.test.ts} - apps/web/e2e/canvas-collab-991.mjs; bench/canvas-collab-991.mjs - contracts/{openapi.json,actions.json,action-overrides.json}; packages/api-client/src/generated.ts - tests/adversarial/xuser_matrix.py UX gaps closed - A Viewer can export without submitting an empty edit event. - Shared plain JSON and its file Copy link open the drawing instead of a JSON Quick Look. - Public raw-byte routes cannot expose private card metadata. - Canvas public-link options no longer offer Edit or linked Notes. New Canvas links default to a 30-day expiry. - Public zoom controls reuse warm tooltips with shortcut hints. Element links use the shared viewport settling duration for pointer and keyboard. - Public drawing revisions retain the visitor viewport. Access removal releases rendered pixels; offline reads keep the last received scene and retry. UX gaps left / known gaps - Collaborate with a Group needs the Instance Group and membership authority. No Group UI or independent Canvas membership store was added. - #977 owns live-card resolution. Until its per-viewer resolver is connected, public scenes and recipient exports conservatively replace all embedded/image references with placeholders. Owner exports remain lossless. #975 owns history. - Visual approval belongs to Claude/the orchestrator. This job attaches real production screenshots and does not approve their visual quality. Decisions - Use two-second serial conditional GETs for public committed revisions instead of public editing-room connections. Pause in hidden pages. - Preserve element IDs and geometry in private placeholders; strip opaque values even under otherwise familiar field names. - Use the same conservative projection for recipient exports until #977 supplies per-viewer reference resolution. - Retry one 404 only for an already-rendered public scene to cover the brief atomic Note-save/Files-index interval. Each retry checks current authority; subsequent denial removes pixels. Password/access denials are immediate. - Do not create a new Group authority in this feature. The required origin/dev merge completed once at 843af4f05. No dependency or migration was added. Verification (gate output verbatim) `cargo fmt --check` exited 0 with no output. core: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 8.67s test result: ok. 538 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.09s test result: ok. 19 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.11s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.05s test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.59s 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 ``` notes: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 12.84s test result: ok. 192 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 200.40s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.89s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` files: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 10.14s test result: ok. 162 passed; 0 failed; 1 ignored; 0 measured; 0 filtered out; finished in 115.93s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` collab: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 20.36s test result: ok. 44 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.00s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.32s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 2.81s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.24s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 39.90s test result: ok. 11 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 5.21s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.96s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.19s test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 10.55s test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 1.84s test result: ok. 5 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 19.04s 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 19.80s test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s ``` server: ```text Finished `dev` profile [unoptimized + debuginfo] target(s) in 23.24s test result: ok. 162 passed; 0 failed; 5 ignored; 0 measured; 0 filtered out; finished in 14.95s ``` Web and registry: ```text svelte-check found 0 errors and 0 warnings Test Files 5 passed (5) Tests 51 passed (51) Action registry: 344 operations, 326 generated tools Cross-User classification gate: 346 operations classified Generated entry point classification: 978 tools classified Ran 12 tests in 0.184s OK ``` The server suite initially found a missing 429 declaration on the new public route. The declaration was fixed; the focused regression and final server suite passed. New browser assertions were corrected for canonical /notes routing, shortened filenames and anonymous OS theme behavior. Existing test expectations were not changed. For the merge round - `bun run --cwd apps/web test --maxWorkers=2`: full web regression coverage. - `bash tests/adversarial/run-split.sh`: authz, cross-user and robustness matrices, including new export and public-read classification rows. Focused live regression covers public POST refusal and mid-session Share/public-link revocation in this branch. - `bun apps/web/e2e/canvas-976.mjs` and `bun apps/web/e2e/canvas-export-976.mjs`: full owner drawing/export behavior with the combined branches. - Real macOS checks under `flock ~/.local/state/codex-jobs/calternal/macvm.lock`, as required by the shared verification policy. No Mac GUI session or staging deployment ran in this job. - Performance review only: on the perf VM with the shared release server configured, `flock /root/perf.lock bash -c 'cat /proc/loadavg; bun bench/canvas-collab-991.mjs --json artifacts/canvas-collab-profile.json'`. No measurements ran here under the current policy. There is no public Canvas projection baseline in docs/perf/baseline.json; numbers remain unmeasured. Focused production browser output (verbatim) ```text Canvas test: three Users registered. Canvas test: collaborator and viewer opened the real scene. Canvas test: anonymous pan/zoom, private placeholders and element links passed. Canvas test: recipient PNG/SVG exports passed. Canvas #991 production regression passed: three Users plus public viewer, strokes, exports, Shared JSON discovery, follow, downgrade, revoke; 48 macOS screenshots. ``` Production build exited 0. Type checks passed after the viewport-motion and shared-tooltip changes. The screenshot set is from that final build. Screenshot-only settling waits do not delay product content or input. Icon/label pairs were inspected at original screenshot resolution; visual approval remains with the orchestrator. Cleanup (verbatim) ```text Removed 19278 files, 13.1GiB total ``` Generated web build, renderer and font output was removed. `git status --short` has no output. No push or deployment ran. Evidence [All 48 macOS production screenshots and gate logs](https://git.kayg.org/attachments/e91b8c81-1841-4243-ba31-87ba346cca2d) | Screen | 390 light | 390 dark | 820 light | 820 dark | 1440 light | 1440 dark | |---|---|---|---|---|---|---| | public | [PNG](https://git.kayg.org/attachments/74626912-f84c-4b1b-9a3a-52d884dc1b84) | [PNG](https://git.kayg.org/attachments/2f3bf6d2-cc0e-4aaf-a77e-26c90d471012) | [PNG](https://git.kayg.org/attachments/25620627-31cf-4b80-80b7-692c3ccb138c) | [PNG](https://git.kayg.org/attachments/542c8e14-db9a-4dad-a0f1-c065968bea02) | [PNG](https://git.kayg.org/attachments/1442ca74-b83d-48f2-9a61-9555937de24e) | [PNG](https://git.kayg.org/attachments/14f66f3a-b810-4e04-a9d3-7c4e3b649c0a) | | shared | [PNG](https://git.kayg.org/attachments/e69d8e2d-37d6-4a11-bb62-3182546aba71) | [PNG](https://git.kayg.org/attachments/1e88c5fd-82e1-4271-a5dd-9bbe64f02a6b) | [PNG](https://git.kayg.org/attachments/b8803e00-7cab-41e0-88ae-3c931df43203) | [PNG](https://git.kayg.org/attachments/c5033b3b-948f-4a67-9f72-3e7ada8f1223) | [PNG](https://git.kayg.org/attachments/105d6adc-335e-4832-94e6-341abdad379f) | [PNG](https://git.kayg.org/attachments/f159d2e5-001f-4a9a-9641-e9295cb92087) | | owner | [PNG](https://git.kayg.org/attachments/43a77ede-4f81-41dc-a449-3a7c11e3651c) | [PNG](https://git.kayg.org/attachments/a28ea481-0272-4975-8267-d75341e7741f) | [PNG](https://git.kayg.org/attachments/f24db568-f103-493a-989f-696cd9ab8c6f) | [PNG](https://git.kayg.org/attachments/406daf9f-e8aa-434c-8c60-18e4f5f6a624) | [PNG](https://git.kayg.org/attachments/073ccf73-5f06-48c1-a828-3c8f34057303) | [PNG](https://git.kayg.org/attachments/ad814feb-8728-4041-a7f9-8875e54c0e13) | | collaborator | [PNG](https://git.kayg.org/attachments/b2c85117-ebe7-4f15-839e-e03bf7e37b5e) | [PNG](https://git.kayg.org/attachments/0848d3b6-bd10-43ec-8805-4ee8eb038bb0) | [PNG](https://git.kayg.org/attachments/6421c4fe-6230-4573-8c1e-94dd30bc9872) | [PNG](https://git.kayg.org/attachments/57a63c0d-2f47-460c-9236-90dfdf8c39ce) | [PNG](https://git.kayg.org/attachments/6764491c-782f-4b5b-b057-d4afd6847be2) | [PNG](https://git.kayg.org/attachments/f421329f-157a-4f2d-a027-a3ea6823d004) | | viewer | [PNG](https://git.kayg.org/attachments/71812eec-050b-429a-aac5-50fe878e6a49) | [PNG](https://git.kayg.org/attachments/4a7c8b6b-d0b2-4c83-99db-006fcc48a95d) | [PNG](https://git.kayg.org/attachments/9bf51c67-e47b-4a65-a519-fa79f3c960c4) | [PNG](https://git.kayg.org/attachments/fae6c87d-5000-4ae0-ba0c-44cfa9a3d628) | [PNG](https://git.kayg.org/attachments/9622713e-93f6-4895-94fa-6dbd496408c8) | [PNG](https://git.kayg.org/attachments/8114464c-69cd-415e-a375-1b80ad9f9975) | | menu | [PNG](https://git.kayg.org/attachments/a01793f2-054c-47fe-a13e-2a356aef8778) | [PNG](https://git.kayg.org/attachments/0cf151d1-a6d0-4d47-bb91-092cc9889984) | [PNG](https://git.kayg.org/attachments/cd513717-5bd3-46f9-b676-d3ffc0ed8b0e) | [PNG](https://git.kayg.org/attachments/26342821-6929-4fe6-82af-5c6a4b22089a) | [PNG](https://git.kayg.org/attachments/3efe3d01-54f4-4442-b38e-3b9852be6033) | [PNG](https://git.kayg.org/attachments/76f398f4-2a07-4735-bc94-3a1c160c8a36) | | share | [PNG](https://git.kayg.org/attachments/2edef100-80b9-4d09-81b1-548f16c8084b) | [PNG](https://git.kayg.org/attachments/36daa820-15f1-4466-b99c-e1b28bc9c45b) | [PNG](https://git.kayg.org/attachments/573ec162-d473-4b84-8964-078b52a8390d) | [PNG](https://git.kayg.org/attachments/3e5502bb-9902-45d2-a583-13d7d40dcdc7) | [PNG](https://git.kayg.org/attachments/adfabc9d-bfa7-4076-b7e6-472452a23a12) | [PNG](https://git.kayg.org/attachments/0724d749-9fd2-41f8-9072-d6397a287006) | | share-links | [PNG](https://git.kayg.org/attachments/c664b485-29dd-4523-be9c-bfa1799360af) | [PNG](https://git.kayg.org/attachments/7681e09c-acdd-4b55-acdf-593cd325a072) | [PNG](https://git.kayg.org/attachments/b38d384c-482a-4714-a235-413acc57c762) | [PNG](https://git.kayg.org/attachments/22048358-831a-409d-aad9-35653ffcd911) | [PNG](https://git.kayg.org/attachments/f07d632e-4336-487d-be7b-159f64da20f4) | [PNG](https://git.kayg.org/attachments/33304302-93f9-4a32-a66b-955b6f39886e) |
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.

Three distinct Users opened live editor/viewer scenes. Live edits, public placeholders/pan/zoom and recipient PNG/SVG exports passed. The full flow stops on the retained long-URL expectation after Shared discovery opens the correct stable /n/ identity. Revoke/reconnect and recipient screenshots remain incomplete.

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. Three distinct Users opened live editor/viewer scenes. Live edits, public placeholders/pan/zoom and recipient PNG/SVG exports passed. The full flow stops on the retained long-URL expectation after Shared discovery opens the correct stable /n/ identity. Revoke/reconnect and recipient screenshots remain incomplete. 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#991
No description provided.