openpencil/packages/CLAUDE.md
Kayshen-X 64d70d672d feat(extension): add OpenPencil web-capture Chrome extension
MV3 extension that captures the rendered active tab (via the shared
snapshot-extractor contract) and imports it into OpenPencil. Logic lives
in the new op-chrome-extension-core crate (wasm): endpoint rules,
chunked-transfer integrity, /mcp envelope + reply classification,
download-name sanitisation, SSO/account session parsing, and hub
snapshot-inbox delivery. JS is glue only (chrome.* APIs, fetch, popup
DOM, injected page functions).

Capture: full page + element pick. Delivery: local ingress
(POST /api/import/web-snapshot) with /mcp fallback, JSON download, and —
when signed in to OP Hub — the account snapshot inbox. Flat popup UI,
15-locale strings with an in-popup language switcher, store packaging.

Why a crate: keeps the security-sensitive logic in tested Rust rather
than glue JS, and the SW/popup split keeps dynamic import() out of the
service-worker graph (guarded by check-sw-imports).
2026-08-04 21:43:01 +08:00

5.6 KiB

Packages

The web SDK packages that remain after the TypeScript retirement. This directory is the npm/Bun workspace root (packages/package.json); the repository root is a pure Cargo workspace with no package.json.

The pen-* packages (pen-types, pen-core, pen-engine, pen-renderer, pen-figma, pen-mcp, pen-ai-skills, pen-sdk, pen-react, pen-acp) and pen-codegen were retired along with apps/*. Their functionality now lives in the Rust crates/ (see crates/CLAUDE.md). Nothing here depends on them. The agent-native Zig runtime was also removed — the built-in agent runtime is now the Rust agent crate (vendor/agent, shared with Zode).

Workspace tooling

Run these from packages/:

  • Lint / format the SDK: bun run lint (oxlint, plus the extension's extractor drift check) / bun run format (oxfmt).
  • Iconify catalog (Rust assets): bun run generate-iconify-catalogscripts/generate-iconify-catalog.mjs reads @iconify-json/* and writes crates/op-editor-ui/assets/iconify-catalog-{core,brands}.json (the icon catalog embedded in / served by the Rust web target).
  • Sync SDK versions: bun run sync-version reads the canonical version from root Cargo.toml and updates all SDK consumers; verify with bun run sync-version:check.

op-web-sdk (op-web-sdk/)

Read-only OpenPencil .op viewer SDK for the web, wasm-backed. Wraps the op-host-web CanvasKit wasm bundle behind a small JS/TS embedding API (mount / load .op / viewport control / zoom-to-fit). Replaces the public role of the retired pen-react (viewing only — editing is not a goal of the public SDK).

  • Zero runtime dependencies; ships its own wasm under wasm/.
  • Build: tsup (bun run build inside the package). Tests: vitest.

op-web-sdk-react (op-web-sdk-react/)

React 19 adapter for op-web-sdk (component + hooks wrapper). Depends only on @zseven-w/op-web-sdk (+ peer react / react-dom).

op-web-sdk-vue (op-web-sdk-vue/)

Vue 3 adapter for op-web-sdk. Depends only on @zseven-w/op-web-sdk (+ peer vue).

op-chrome-extension (op-chrome-extension/)

Manifest V3 Chrome extension that captures the rendered active tab and imports it into a running OpenPencil. No dependencies, not a Bun workspace member — load it unpacked from chrome://extensions.

  • The logic is Rust. crates/op-chrome-extension-core (endpoint rules, chunked-transfer integrity, /mcp envelope + reply classification, download-name sanitisation) compiles to wasm; packages/op-chrome-extension/scripts/build-wasm.sh runs cargo build --target wasm32-unknown-unknown + wasm-bindgen --target web and installs the shim + module into op-chrome-extension/wasm/, which is gitignored like every other wasm-bindgen output in the repo. Build it before "Load unpacked" — the popup reports an actionable error otherwise. Unit-test the logic natively with cargo test -p op-chrome-extension-core.
  • The JS that remains is glue: chrome.* calls, fetch, popup DOM, and the functions injected into the captured tab (which must be JS — they run in the page's process). The manifest declares 'wasm-unsafe-eval' in content_security_policy.extension_pages, which is what MV3 requires to instantiate a bundled .wasm; no code is fetched from outside the package.
  • The capture engine is vendor/snapshot-extractor.js, a byte-identical copy of crates/op-html/assets/snapshot-extractor.js (the contract op_html::import_snapshot parses). bun run lint runs op-chrome-extension/scripts/check-extractor-sync.sh, which fails on drift; --fix re-copies the canonical asset. Never edit the copy.
  • Ingress: POST /api/import/web-snapshot on the desktop app's live MCP endpoint (insert-only, tokenless, the one route there that accepts a chrome-extension:// origin — see crates/op-host-services/src/mcp_live/snapshot_ingest.rs), falling back to a plain /mcp tools/call import_web_snapshot for the unmanaged --serve-web daemon. Download JSON + op import:snapshot is the offline path.
  • Account (optional). The popup header can sign in to OP Hub through the hub's own BFF: a tab on GET /api/v1/auth/login?return_to=/account, then GET /api/v1/session with credentials: 'include'. The extension is a public client — no SSO secret, no token, no cookies permission; the hub's HttpOnly session cookie stays in the browser's jar. Regions cn / global map to https://op.zseven.cn / https://op.zseven.tech, both in host_permissions, and account.rs asserts the manifest agrees.
  • Account delivery (optional, signed in). POST <hub>/api/v1/snapshots with credentials: 'include' + X-CSRF-Token, i.e. op-hub's per-user snapshot inbox. The envelope, the page-title-derived name, the 32 MiB ceiling and the reply classification are Rust (hub.rs / hub_reply.rs); delivery.rs remains the single rule that decides local vs account, and an expired session collapses to local. The service worker's element-pick flow runs its own session probe (account.js is in the worker's static graph and stays dynamic-import-free). Contract history: op-chrome-extension/docs/hub-inbox-api-proposal.md.
  • Store packaging. bun run package-extension (or op-chrome-extension/scripts/package-extension.sh) builds the wasm, runs the tests and all four guards, stages only the runtime files, copies pt to pt_BR/pt_PT for Chrome's manifest-locale lookup, and writes op-chrome-extension/dist/op-chrome-extension-<version>.zip (gitignored). The listing's privacy policy is op-chrome-extension/docs/privacy-policy.md.