Skip to content

feat(canvas): add snapshot sharing - #768

Merged
Neonforge98 merged 11 commits into
org2AI:developfrom
beruro:junyu/canvas-share
Aug 14, 2026
Merged

feat(canvas): add snapshot sharing#768
Neonforge98 merged 11 commits into
org2AI:developfrom
beruro:junyu/canvas-share

Conversation

@beruro

@beruro beruro commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Problem

Canvas needs a dedicated way to publish one completed prototype without sharing the surrounding conversation, session, repository, account, or later revisions. Embedding every compressed snapshot in the URL creates very long links, while reusing ORG2 Cloud session sharing applies the wrong data scope. A failed short-link upload also left a permanently cached long fallback, so the same Canvas kept showing “short-link service unavailable” after recovery.

Solution

  • Add a visible Canvas toolbar Share action with reason-specific disabled states.
  • Serialize only a versioned public Canvas snapshot, compress it, prefer a hosted short link, and fall back to the self-contained format when upload is unavailable.
  • Use https://canvas.org2.dev/ and its same-origin /api/canvas-shares endpoint.
  • Keep generation above tab remounts in a bounded 16-entry / 1 MiB app-runtime LRU with single-flight requests and stale-completion guards.
  • Treat self-contained fallbacks as recoverable: cache them for five minutes, retry lazily on the next open after expiry, and expose a localized “Retry short link” action that keeps the full link usable while retrying.
  • Coalesce concurrent retries for the same snapshot. No timer, polling, or idle network work is added.

The owned viewer and proxy are deployed at https://canvas.org2.dev/ and tracked in ORGII-cloud-infra#20.

Potential risks

  • Infrastructure PR fix(Skill): Skills Menu & Import Skill bug #20 remains Draft because its repository-wide Gitleaks check flags an unrelated historical README finding. This desktop PR is ready for review, but must not be merged until that source PR is merged.
  • If upload is unavailable, a self-contained fallback exceeding 64 KiB remains unshareable until recovery.
  • Recovery is demand-driven: user retry or reopening after five minutes; there is no background polling.
  • Links are public to anyone with the URL. Hosted IDs are unlisted, not encrypted, and currently expire after one year.
  • Link generation requires the WebView CompressionStream API.
  • Existing personal-host links remain available for backward compatibility.

State and edge coverage

Journey Result
Stable shareable Canvas Short link when upload succeeds
Upload failure Usable self-contained fallback
Service recovery Manual retry preserves the full link; post-TTL reopen retries lazily
Concurrent retry consumers One shared in-flight request
Empty/streaming/local/oversized source Share remains visible but disabled, or returns a bounded error
Close/unmount/snapshot switch Operation generation rejects stale completion
Cache pressure Bounded LRU evicts and aborts pending work

Audit

Architecture coverage included state/types, privacy boundaries, cache/runtime ownership, configuration limits, and wire validation. Rust/backend layers were skipped because this PR changes only the desktop frontend 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, retry, or post-TTL reopen No polling or idle retry Recovery tests
Memory fix App-runtime LRU 16 entries, 1 MiB, five-minute fallback TTL Bound/expiry tests
Scope/isolation keep Normalized public snapshot key Single-flight plus operation IDs Remount/coalescing/stale-result tests
Rendering/hot path keep Memoized eligibility No added hot-path work Toolbar tests

Performance verdict: pass. There is no idle resource; memory is bounded; fallback freshness is checked lazily; concurrent retries are single-flight.

Effects: no Effect was added or materially changed. The existing cleanup Effect only invalidates a pending promise subscriber on unmount.

Verification

  • Focused ESLint on the seven changed TypeScript/TSX files — passed.
  • Focused Vitest — passed, 3 files / 19 tests.
  • pnpm typecheck after merging latest develop — passed.
  • English, Simplified Chinese, and Traditional Chinese locale JSON/Prettier checks — passed.
  • Husky staged-file lint/type/hygiene checks and git diff --check — passed.
  • git rev-list --left-right --count origin/develop...HEAD0 9.
  • Production root, short route, self-contained route, desktop-origin CORS preflight, valid POST, and invalid POST were smoke-tested successfully.
  • GitHub CI — Frontend (typecheck · lint · test), Rust (clippy), and AI attribution all passed.

UI evidence

The live viewer is available at https://canvas.org2.dev/. The fallback dialog keeps the complete link usable and adds a localized, loading-aware “Retry short link” action. Visible, loading, and disabled states have render-test coverage.

@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
@beruro
beruro marked this pull request as ready for review August 13, 2026 14:52
- isPublicWebUrl rejects loopback/RFC1918/link-local/IPv6-private and
  .local/.internal hosts on encode and decode
- distinct short-link-unavailable-too-large error so a service outage is
  not reported as an oversized canvas
- unsupported modes rejected at availability/create; newer-version links
  distinguished from corruption
- LRU accounting includes cached result links; misconfigured API URL
  fails loudly instead of laundering into service-unavailable; eviction
  aborts are silent to subscribers and covered by tests; titles truncate
  on code-point boundaries

(cherry picked from commit b59aebc3ea51242e5e5c8c7605a28947971bafec)
Add the canvasApp.share* key set and canvasApp.retry to en/zh/zh-Hant
sessions.json, matching the 768 subset of cc0a56b83. Keys for other
PRs (design*/revision*/compare*/domSelection/tools/input/manageIssues)
are intentionally excluded; existing shareDialogRetryShort and
shareDialogRetryingShort keys are kept in place.
@Neonforge98
Neonforge98 merged commit e9b08d4 into org2AI:develop Aug 14, 2026
3 checks passed
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.

3 participants