How the extension system is tested, layer by layer, and the verification log for branch feat/extensions. The layers
follow design.md §9; "planned" means the layer is specified but the test does not exist on the
branch yet.

Test layers

Layer

Where

What it proves

Status

Client unit

frontend/packages/client/src/extensions.test.ts

Manifest and install-record schemas, parseExtensionInstalls, resolveExtensionMount (longest prefix, sub-path), buildSignDataPayload, message guards, dev-override storage

exists

SDK unit

frontend/packages/extension-sdk/src/{connect,base64,theme}.test.ts

Handshake with retries and timeout, request/response multiplexing, error mapping to ExtensionError, context events, disconnect, base64 round-trips, applyTheme / injectBaseStyles — all through an in-memory ExtensionTransport, no browser

exists

UI bridge-server unit

frontend/packages/ui/src/__tests__/extension-{bridge-server,host-handlers,host-utils}.test.ts

Source check, param validation → invalid_params, EXTENSION_METHOD_PERMISSIONS enforcement → permission_denied, unknown/missing methods, one response per id, hm:// id unpacking per key, toCloneable, metadata merge / body replace change building, confirm flow and session allow

exists

CLI fixture

frontend/apps/cli/src/test/extension.test.ts

Against a real daemon: publish (dry-run, invalid manifest, document with nested manifest + README body), inspect (refuses non-extensions), install (pinned record, --force, bad mounts, --latest), list, update (no-op when current, re-pin after republish), uninstall

exists

Web server fixture

frontend/apps/cli/src/test/ (with the web server the fixture starts)

GET /<mount> on a site with an install returns the extension page shell (site header + frame placeholder) rather than the document page

planned <!-- TODO: add to extension.test.ts or a web fixture test -->

Browser (Playwright)

tests/extensions.browser.integration.test.ts

Against the fixture daemon + built web app in headless Chromium: publishes hello-signer as an extension document under the fixture site and installs it (pinned) at /hello with the same helpers the CLI uses; GET /<mount> is SSR'd (200, <title> carries the mount); the sandboxed srcdoc iframe has exactly allow-scripts allow-forms allow-popups allow-modals allow-downloads; hello completes (context panel shows the extension id, version, platform: web, dev: false); storage.set round-trips; sign.data without a signed-in user fails not_signed_in and opens no dialog; gateway form /hm/<uid>/<mount> mounts the same frame; ?extdev=<url> switches the iframe to src=<url> with the dev banner, persists across loads, ?extdev=off clears it; no pageerrors. Not covered: the sign-confirmation dialog + approval path (needs a signed-in web user; see manual QA)

exists

Manual QA on mainnet

desktop + web against the examples published under the starlight space

Install / pin / update / remove flows in Site settings, the dev banner and ?extdev=, sign dialogs on both platforms, theme switching

planned

Running what exists

# client schemas + protocol helpers cd frontend/packages/client && npx vitest --run src/extensions.test.ts # SDK pnpm --filter @seed-hypermedia/extension-sdk test # CLI fixture (starts a daemon; slow) cd frontend/apps/cli && bun test src/test/extension.test.ts # browser test (builds the web app, starts a daemon + web server, drives headless Chromium; ~1 min after the build) cd tests && pnpm test:install-browsers # once, installs Chromium cd tests && SKIP_BUILD=false pnpm vitest --run extensions.browser.integration.test.ts cd tests && SKIP_BUILD=true pnpm vitest --run extensions.browser.integration.test.ts # reuse the last web build

Typecheck everything the branch touches with pnpm typecheck from the repo root; the examples build with
pnpm --filter @seed-extensions/hello-signer build (and site-dashboard, kanban).

Manual QA script


    Publish the three examples from extensions/examples with seed-cli extension publish under the starlight key.

    In desktop Site settings → Extensions, install hello-signer at hello (pinned). Open /hello; confirm the context

panel shows platform: desktop, your account, and dev: false.

    Press every hello-signer button: Sign this text and Post comment must open the confirmation dialog; Deny must

log user_rejected; Approve with "Allow … for the rest of this session" must skip the dialog on the next call.
Storage counter persists across a reload; Navigate to site home leaves the page; setRoute updates the URL and
the context's subPath.

    Repeat step 2–3 on the web app for the same site, signed in through the vault; confirm platform: web and that the

comment appears on the home document.

    Run pnpm dev in extensions/examples/hello-signer, open /hello?extdev=http://localhost:5181 on web (and set the

override in desktop Settings → Advanced); the dev banner shows, dev: true, an edit to main.ts hot-reloads. Clear
with the banner / ?extdev=off.

    Install site-dashboard at dashboard and kanban at board. Kanban: create the board (first save creates the

document), move a card, save, reload from the network, confirm the document at /board has the kanban metadata via
seed-cli document get.

    Republish hello-signer with a bumped version; the Extensions tab shows Update to latest; update; the context

shows the new extensionVersion. Remove the install; /hello renders the (empty) document page again.

    Toggle the app theme; the extension follows (data-theme on its <html>).

Verification log

Date

What

Result

Notes

2026-08-31

client/src/extensions.test.ts (31 tests) and extension-sdk (3 files, 20 tests)

pass

npx vitest --run in each package; jsdom environment for the SDK

2026-08-31

@shm/ui src/__tests__/extension-{bridge-server,host-handlers,host-utils}.test.ts

pass

part of the "@shm/ui extension + resource-page suites 100 pass" run below (npx vitest run src/__tests__/extension-*.test.ts in frontend/packages/ui)

TODO

cli/src/test/extension.test.ts (11 tests) against the fixture daemon

—

file exists; run and record the result

TODO

Web server GET /<mount> fixture

—

not yet written

2026-08-31

tests/extensions.browser.integration.test.ts (4 tests), headless Chromium 143

pass

SKIP_BUILD=false pnpm vitest --run extensions.browser.integration.test.ts in tests/: 58 s including the web build, 22 s with SKIP_BUILD=true (two consecutive green runs). hello-signer published at hm://<fixture>/extensions/hello-signer and installed pinned at /hello; /hello and /hm/<uid>/hello both SSR 200 with the sandboxed srcdoc iframe; hello → context, storage.set → counter: 1, sign.data → not_signed_in (rendered by hello-signer as "Sign in to the site first") with no extension-sign-confirm dialog; ?extdev=http://127.0.0.1:1/ → src override + dev banner, ?extdev=off → back to srcdoc. No page errors.

2026-08-31

Manual QA, desktop dev app (pnpm dev:debug, mainnet, driven over CDP :9222)

pass

Address-bar navigation to hm://z6MkiAK…/hello, /dashboard, /board: site header shows the installed mounts as nav items (Home / Board / Dashboard / Hello); sandboxed srcdoc iframe; context reports platform: desktop and the selected account; sign.data confirmation dialog → 64-byte signature via daemon SignData; storage, ui.toast; dashboard lists 12 documents; the board created from the web (Ship extensions card) synced and rendered; saving as an account without write capability on the site is refused with the daemon's capability error (permission_denied); navigating /board → /board/card/abc keeps the same iframe alive (marker survived) and delivers the new subPath. Positive save as the site owner was verified on web (the desktop instance does not hold the Starlight key). Space settings → Extensions tab: empty state, "Install an extension" form fetches hm://…/extensions/examples/hello-signer and previews name / version / kind / description / permissions, proposes mount path hello, pins by default.

2026-08-31

Multi-agent review workflow (6 lenses: security, host correctness, web, desktop, CLI/SDK/examples, docs; 26 agents) → 18 confirmed findings + 8 doc inaccuracies, all fixed: loopback-only ?extdev=, session grants scoped to code source with dev-override warning, Approve arming (500 ms), always-confirm for extensions/seedExtension keys, /\\evil.com navigation, file proxy CID validation, no frame reload on adapter churn, metadata shape-change diff (host + CLI), comment thread root, file.read hard cap, nested-mount revalidation, concurrent home fetch, desktop discovery for extension docs, drafts never shadowed, all document views shadowed on desktop, kanban empty-board round trip / clear fields / drag class. Re-verified: web anonymous + signed flows and desktop CDP drive pass; Approve disabled at open and enabled after 500 ms; typecheck clean across 10 packages; client 49, ui 112, sdk 20, web 252, desktop 11, cli 168+16 tests pass.

pass

see decisions.md for the ?extdev= rationale

2026-08-31

Package suites on the branch: @shm/shared 1042 pass, @shm/web 242 pass, extension-sdk 20 pass, @shm/ui extension + resource-page suites 100 pass; @seed-hypermedia/client 306 pass with 2 pre-existing failures unrelated to this branch (client.test.ts Search mock lacks nextPageToken; vault-local.test.ts resolves the machine's real ~/Library/Application Support/Seed/daemon/vault.json) — both fail identically on main's code paths, none of the touched files are involved. @shm/ui comments.test.tsx also fails on main (its routes mock lacks agentRouteSchema).

pass

typecheck clean for client, shared, ui, extension-sdk, web, desktop, cli

2026-08-31

Manual QA, web dev server (:3000) against mainnet starlight, headless Chromium

pass

Examples published under hm://z6MkiAK…/extensions/examples/* with seed-cli extension publish, installed at /hello, /dashboard, /board with extension install (pinned). Gateway-form pages /hm/<uid>/<mount> SSR with title Hello · Starlight; sandboxed srcdoc iframe (allow-scripts allow-forms allow-popups allow-modals allow-downloads, no allow-same-origin); hello → context, storage.set/get round-trip, ui.toast; sign.data without a user → not_signed_in. With a local device key: sign.data confirmation dialog (hex preview, domain-separation note) → 64-byte signature; sign.comment dialog → comment stored in the daemon (verified with seed-cli comment list); kanban sign.document as a non-member → daemon capability error surfaced as permission_denied; as the site owner → hm://…/board created with metadata.kanban (array survived), card present after reload. Dashboard lists 11 documents with comment counts and recent activity. Found + fixed: bridge server disposed under React StrictMode (never answered hello).

Do you like what you are reading? Subscribe to receive updates.

Unsubscribe anytime