Skip to content

FE-1669: Share Petrinaut snapshots through self-contained URLs - #9680

Open
kube wants to merge 3 commits into
codex/fe-1660-readonly-feedbackfrom
codex/fe-1669-snapshot-sharing
Open

kube wants to merge 3 commits into
codex/fe-1660-readonly-feedbackfrom
codex/fe-1669-snapshot-sharing

Conversation

@kube

@kube kube commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Local document URLs identify files saved in one browser. This adds Share to create a link containing a fixed copy of the complete net, with an option to include the current editor view.

Recipients open the snapshot read-only and can make an editable local copy. Later edits to the sender's document do not change the link.

Links

Blocked by

Changes

  • Encode compact document JSON with Brotli quality 11 in a versioned /share#v1.br.<payload> fragment
  • Preserve the optional editor location through view changes, browser history, and local copying
  • Run compression in a worker with cancellation, a timeout, and limits on link length and decompressed size
  • Offer YAML download for large snapshots and manual copying when clipboard access fails
  • Remove snapshot fragments from Sentry events, breadcrumbs, transactions, and spans
  • Add the sharing guide and register it in the AI documentation catalog, with patch changesets for both published packages

Test coverage

After the 14 September rebase, affected package builds, unit tests, types, lint and architecture checks passed on the integrated stack. Browser recordings and manual checks listed below predate this rebase.

  • Codec tests round-trip every core example and check Unicode, independent Brotli compatibility, malformed and unsupported links, truncated data, and size limits
  • Route tests cover opening with empty storage, read-only handles, view restoration, Back/Forward, replacing a snapshot, and copying to a local UUID
  • Dialog and worker tests cover fixed capture, optional view inclusion, clipboard failure, file fallback, cancellation, startup errors, and timeouts
  • Telemetry tests verify removal of valid, invalid, and future snapshot fragments
  • Builds, unit tests, type checks, lint, formatting, and architecture checks pass for the affected packages
  • Manually verified the development and production builds: shared an example and an edited local document, opened the link on a separate origin, restored the Scenarios view, triggered the read-only drag notice, and made a local copy

How to test

  • Open the website preview
  • Select Simulate > Scenarios, then Share
    • Choose Copy snapshot link
  • Open the link in another browser

    The complete snapshot opens in the Scenarios view without requiring the sender's local storage.

  • Select Edit and try dragging a place

    A read-only notice offers Make a local copy.

  • Choose Make a local copy

    An editable copy opens at a new /local/<UUID> URL.

  • Choose Share and clear Include current view

    The link opens the snapshot at its default view.

@kube kube self-assigned this Sep 11, 2026
@vercel

vercel Bot commented Sep 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
hash Ready Ready Preview Sep 15, 2026 8:50pm UTC
petrinaut Error Error Sep 15, 2026 8:50pm UTC
petrinaut-docs Error Error Sep 15, 2026 8:50pm UTC
1 Skipped Deployment
Project Deployment Actions Updated
hashdotdesign-tokens Ignored Ignored Preview Sep 15, 2026 8:50pm UTC

Request Review

@github-actions github-actions Bot added area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team area/apps labels Sep 11, 2026
@kube
kube added this pull request to stack #9663 September 11, 2026 14:51
): Uint8Array => {
if (hash.length > maxSnapshotHashLength) throw new SnapshotError("too-large");
if (!hash.startsWith(prefix)) {
throw new SnapshotError(/^v\d+\./u.test(hash) ? "unsupported" : "invalid");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semgrep identified an issue in your code:
Ensure that the regex used to compare with user supplied input is safe from regular expression denial of service.

To resolve this comment:

🔧 No guidance has been designated for this issue. Fix according to your organization's approved methods.

💬 Ignore this finding

Reply with Semgrep commands to ignore this finding.

  • /fp <comment> for false positive
  • /ar <comment> for acceptable risk
  • /other <comment> for all other reasons

Alternatively, triage in Semgrep AppSec Platform to ignore the finding created by regex_dos.

You can view more details about this finding in the Semgrep AppSec Platform.

): Uint8Array => {
if (hash.length > maxSnapshotHashLength) throw new SnapshotError("too-large");
if (!hash.startsWith(prefix)) {
throw new SnapshotError(/^v\d+\./u.test(hash) ? "unsupported" : "invalid");
@cursor

cursor Bot commented Sep 11, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
New client-side codec and URL fragment handling with size limits and telemetry redaction; snapshot links expose full net content to anyone with the link but do not change server auth or persistence.

Overview
Adds Share on the demo site so users can distribute a fixed copy of a complete net via self-contained /share#v1.br.<payload> URLs, with optional query params for the current editor view.

Encoding and opening: Documents are serialized to canonical JSON, Brotli-compressed (quality 11) in a dedicated worker with abort, 30s timeout, and caps (2 MiB document, 16k fragment). Oversized snapshots fall back to YAML download; the Share dialog supports clipboard failure and “include current view.”

Routing and UI: New /share route opens snapshots read-only without touching local storage; Make a local copy forks to /local/<uuid> and keeps the view. ReadonlyDocumentPage centralizes readonly example and share behavior (including Share); editable local docs get Share in the top bar.

Privacy and docs: Sentry hooks strip snapshot fragments from events, breadcrumbs, transactions, and spans. User guide sharing.md and AI readPetrinautDoc catalog entry document the flow; patch changesets for @hashintel/petrinaut and @hashintel/petrinaut-core.

Reviewed by Cursor Bugbot for commit 05755ea. Bugbot is set up for automated code reviews on this repo. Configure here.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Clipboard completion can report a stale URL as copied, and the critical oversized-snapshot download fallback is not exercised.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds self-contained, read-only Petrinaut snapshot sharing with optional editor-view restoration, local copying, and file fallback.

Changes:

  • Adds versioned Brotli snapshot encoding through cancellable workers.
  • Adds sharing UI, /share routing, local-copy behavior, and Sentry redaction.
  • Adds comprehensive documentation, tests, dependency updates, and changesets.
File summaries
File Description
yarn.lock Locks the Brotli dependency.
libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel/petrinaut-docs-content.ts Registers sharing documentation.
libs/@hashintel/petrinaut/docs/sharing.md Documents snapshot sharing.
libs/@hashintel/petrinaut/docs/README.md Links the sharing guide.
libs/@hashintel/petrinaut-core/src/ai.ts Exposes sharing documentation to AI.
apps/petrinaut-website/src/sharing/snapshot.ts Serializes and validates snapshots.
apps/petrinaut-website/src/sharing/snapshot.test.ts Tests snapshot codec behavior.
apps/petrinaut-website/src/sharing/snapshot-worker.ts Runs Brotli operations in a worker.
apps/petrinaut-website/src/sharing/snapshot-worker-protocol.ts Defines worker messages.
apps/petrinaut-website/src/sharing/snapshot-codec.ts Implements bounded Brotli encoding.
apps/petrinaut-website/src/sharing/snapshot-client.ts Manages worker lifecycle and URLs.
apps/petrinaut-website/src/sharing/snapshot-client.test.ts Tests cancellation and failures.
apps/petrinaut-website/src/sharing/share-snapshot-button.tsx Adds the sharing dialog.
apps/petrinaut-website/src/sharing/share-snapshot-button.test.tsx Tests sharing interactions.
apps/petrinaut-website/src/sentry/strip-snapshot-links.ts Redacts snapshot payloads.
apps/petrinaut-website/src/sentry/strip-snapshot-links.test.ts Tests telemetry redaction.
apps/petrinaut-website/src/sentry/instrument.ts Applies redaction to Sentry hooks.
apps/petrinaut-website/src/routes/share.tsx Adds the snapshot route.
apps/petrinaut-website/src/routes/-share.test.tsx Tests snapshot navigation and copying.
apps/petrinaut-website/src/main/app/readonly-document-page.tsx Extracts shared read-only document UI.
apps/petrinaut-website/src/main/app/local-storage-demo/local-storage-demo-app.tsx Adds sharing to local documents.
apps/petrinaut-website/src/examples/full-example-page.tsx Reuses the read-only document page.
apps/petrinaut-website/README.md Documents snapshot architecture.
apps/petrinaut-website/package.json Adds brotli-wasm.
.changeset/snapshot-sharing-guide.md Records the Petrinaut documentation change.
.changeset/snapshot-sharing-doc-catalog.md Records the core catalog change.
Review details
  • Files reviewed: 25/26 changed files
  • Comments generated: 2
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +73 to +80
const copy = async () => {
if (url === null) return;
try {
await navigator.clipboard.writeText(url);
setCopyState("copied");
} catch {
setCopyState("failed");
}
Comment on lines +90 to +93
expect(screen.getByRole("button", { name: "Download file" })).toHaveProperty(
"disabled",
false,
);
SnapshotResponse,
} from "./snapshot-worker-protocol";

self.onmessage = async (event: MessageEvent<SnapshotRequest>) => {
@kube
kube force-pushed the codex/fe-1669-snapshot-sharing branch from 65eeb68 to 655507a Compare September 15, 2026 01:18
@kube
kube removed this pull request from stack #9700 September 15, 2026 02:01
@kube
kube force-pushed the codex/fe-1669-snapshot-sharing branch from 655507a to 28c82d2 Compare September 15, 2026 02:01
@kube
kube added this pull request to stack #9732 September 15, 2026 02:03
@kube
kube force-pushed the codex/fe-1669-snapshot-sharing branch from 28c82d2 to 0f7f66f Compare September 15, 2026 02:30
@kube
kube force-pushed the codex/fe-1669-snapshot-sharing branch from 0f7f66f to bb2a502 Compare September 15, 2026 20:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/apps area/deps Relates to third-party dependencies (area) area/infra Relates to version control, CI, CD or IaC (area) area/libs Relates to first-party libraries/crates/packages (area) type/eng > frontend Owned by the @frontend team

Development

Successfully merging this pull request may close these issues.

3 participants