Skip to content

feat(canvas): add snapshot sharing - #768

Draft
beruro wants to merge 4 commits into
org2AI:developfrom
beruro:junyu/canvas-share
Draft

feat(canvas): add snapshot sharing#768
beruro wants to merge 4 commits into
org2AI:developfrom
beruro:junyu/canvas-share

Conversation

@beruro

@beruro beruro commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

Canvas has no dedicated way to publish one completed prototype. Reusing ORG2 Cloud session sharing applies the wrong eligibility and data scope, while embedding the whole compressed snapshot in the URL creates very long links. Link generation also lived at the mounted dialog boundary, so switching workstation tabs destroyed the result and generated/uploaded the same immutable Canvas again.

The missing invariant was a Canvas-only publication boundary with an explicit lifecycle: only the selected, stable Canvas may be serialized; session, conversation, repository, event, and account data must never cross it; completed links must remain reusable across Canvas tab remounts without creating unbounded retained state. The public URL also needs to be owned by ORG2 instead of depending on a personal GitHub Pages hostname.

Solution

  • Add a visible Canvas toolbar Share action with reason-specific disabled states for empty, changing, local-URL, and oversized canvases.
  • Introduce a versioned CanvasShareEnvelopeV1 containing only mode, title, content, and url; validate source size and public URL rules before encoding.
  • Gzip and base64url-encode the immutable snapshot, prefer an HTTPS-hosted short link, validate the opaque response, and fall back to the backward-compatible self-contained fragment when upload, timeout, rate-limit, or service availability prevents short-link creation.
  • Use https://canvas.org2.dev/ for both hosted and self-contained links and https://canvas.org2.dev/api/canvas-shares for uploads. Regression coverage fixes that owned-origin contract for all three paths.
  • Model the dialog as closed -> preparing -> ready/error, with copy feedback, retry, operation generations, and stale-completion rejection.
  • Move successful and in-flight generation above the tab component into a bounded app-runtime LRU: at most 16 snapshots and 1 MiB of retained snapshot characters. Duplicate opens share one promise, expired short links regenerate, failures are not cached, and eviction aborts pending work.
  • Document the cross-repository wire contract and add UI-consistency audit reports.

The owned viewer and same-origin Vercel proxy are introduced by ORGII-cloud-infra#20.

Potential risks

  • The owned URL depends on the Draft infrastructure PR above. Its Vercel project, custom-domain binding, DNS, and production smoke tests are not complete, so this PR is Draft and must not merge or release until https://canvas.org2.dev/#/s/<id> and #/share/g1/<payload> both pass production checks.
  • If upload is unavailable, the desktop falls back to a self-contained link; a snapshot whose fallback link exceeds 64 KiB remains unshareable until the service recovers.
  • Links are public to anyone who has the full URL. Hosted IDs are unlisted, not encrypted, and expire according to the service response (currently one year); the dialog states this scope.
  • Link generation requires the WebView CompressionStream API. Unsupported runtimes show a recoverable error instead of emitting an incompatible payload.
  • Existing https://beruro.github.io/canvas-share/ links remain available only for backward compatibility. Rollback is to revert the desktop default origin and the infrastructure domain/project binding; already-issued legacy links are unaffected.

State and edge coverage

Journey Result
Stable HTML/React/A2UI or public URL Short link when upload succeeds
Upload timeout/offline/429/5xx/invalid response Self-contained link fallback
Empty, streaming, local URL, or oversized source Share stays visible and disabled, or generation returns a bounded error
Close or unmount during generation The old dialog subscriber is superseded and cannot reopen
Return to the same Canvas tab Reuses ready or in-flight generation without a duplicate upload
Select a newer snapshot while old work finishes Operation generation prevents stale UI overwrite
Expired short link Regenerates instead of reusing the expired result
Cache pressure LRU/count/character bounds evict and abort pending work

Audit

Architecture audit covered domain vocabulary, discriminated state/types, click-to-network control flow, privacy data flow, configuration/limits, dead-code sweep, cache/runtime behavior, initialization defaults, wire validation, and viewer/API resolver symmetry. Rust/backend layers were intentionally skipped because this PR changes only the desktop frontend and its external HTTPS contract.

Frontend UI audit: 0 fixes recommended, 15 elements kept with documented reason, 0 abstraction candidates.

Area Verdict Evidence Change or reason kept Verification
Background work keep Work starts only on Share; upload owns one 8-second timeout and removes abort listeners in finally No polling or idle work Protocol fallback/abort tests and source trace
Memory fix App-runtime entries retain immutable snapshot keys/results 16-entry and 1 MiB LRU bounds; eviction aborts pending work Entry-count and retained-character bound tests
Scope/isolation keep Cache key is the normalized public snapshot and contains no identity/session fields Identical public snapshots may safely reuse one result; operation IDs reject stale UI writes Privacy round-trip, remount, in-flight coalescing, expiry, and stale-result tests
Rendering/hot path keep Eligibility is memoized by selected payload and uses string-length fast paths UTF-8 allocation happens only for ambiguous large inputs Allocation spy and toolbar gating tests

Performance verdict: pass. There is no idle resource, all active resources have a deadline or bounded owner, retained memory is capped, and applicable lifecycle invariants have focused regression coverage.

Effects: one lifecycle cleanup Effect in useCanvasShareDialog; it invalidates the pending promise subscriber on unmount so a stale completion cannot write into the remounted UI. No timer, listener, or network resource is owned by that Effect. The owned-origin update adds or changes no Effects.

Verification

  • Original feature verification: pnpm verify:quick -- src/engines/Simulator/apps/canvas/CanvasApp.tsx src/engines/Simulator/apps/canvas/CanvasApp.share.test.ts src/features/CanvasShare/CanvasShareDialog.tsx src/features/CanvasShare/canvasShareCache.ts src/features/CanvasShare/canvasShareProtocol.ts src/features/CanvasShare/canvasShareProtocol.test.ts src/features/CanvasShare/index.ts src/features/CanvasShare/useCanvasShareDialog.ts src/features/CanvasShare/useCanvasShareDialog.test.ts — passed; 9 files linted and 24 focused tests passed.
  • Original final verification: pnpm verify:final — executed a real full TypeScript check and passed after the test-only fixture correction.
  • After merging the latest origin/develop, the current branch does not contain the repository's documented verify:quick / verify:final package scripts. pnpm verify:quick -- src/features/CanvasShare/canvasShareProtocol.ts src/features/CanvasShare/canvasShareProtocol.test.ts was attempted and returned Command "verify:quick" not found; no unsupported success claim is made for that wrapper.
  • pnpm exec eslint src/features/CanvasShare/canvasShareProtocol.ts src/features/CanvasShare/canvasShareProtocol.test.ts — passed.
  • pnpm exec vitest run src/features/CanvasShare/canvasShareProtocol.test.ts — passed, 1 file / 10 tests.
  • pnpm typecheck — executed the branch's full tsc --noEmit --pretty false check and passed; no cache was used by this script.
  • git diff --check — passed before commit.
  • Husky pre-commit — lint-staged, scoped TypeScript checking, and staged-file hygiene passed.
  • git rev-list --left-right --count origin/develop...HEAD after the final fetch — 0 4; the branch contains the latest develop with no base-only commits.
  • Production canvas.org2.dev smoke testing was not run because infrastructure PR fix(Skill): Skills Menu & Import Skill bug #20 has not yet been deployed or bound to the domain.

UI evidence

Manual UI screenshots were exercised in the originating desktop task (toolbar action, generated short/fallback viewer, and cross-tab repro). They are not attached here because the CLI PR update path does not provide an authenticated image-upload surface. The owned-origin update has no intentional desktop visual delta; production viewer evidence must be added after infrastructure PR #20 is deployed and before this PR is marked ready.

@beruro
beruro requested a review from Harry19081 August 10, 2026 13:31
@beruro
beruro marked this pull request as draft August 11, 2026 15:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants