Document apps
Agent-built HTML apps that live on a Seed document, sync like any file, and read Hypermedia as the viewer. Working note, 2026-10-20.

Document apps

Working note for PR #1262 (feat/document-apps, draft, targets main). It replaces #1139's model (session-private apps on a loopback host), which Eric rejected on 2026-10-20: apps live at a normal hm:// URL inside an account space, and all apps are made that way. #1139 was closed as superseded the same day.

Problem

On #1139 an app the agent builds is a session-private attachment (application/vnd.seed.app+json), readable only by the session owner and served from an ephemeral loopback listener. It cannot be shared: there is no document, no CID, nothing another desktop could sync.

Decision

An app is a UnixFS folder with an index.html referenced from a document's metadata. Sharing the document shares the app: regular document sync plus IPFS file fetch bring the folder to the other desktop.

    Metadata field app on the document: ipfs://<cid> of the folder (a bare UnixFS file is accepted too and treated as index.html). Documented in hypermedia/metadata.md and hypermedia/apps/document-apps.md.

    Origin per document, seed-app://<digest of the hm id>/ on desktop, so localStorage survives app revisions but no two documents share state. The author controls both the document and its app, so that is the right trust boundary. Agreed with Eric 2026-10-20.

    Sandbox: the app is untrusted code once it can be shared. default-src 'self': it loads only its own folder; no network, no daemon, no secret, no parent access. Its one channel is the bridge.

    Bridge: /.seed/client.js, served inside the app origin, defines window.seed (promises over postMessage): context(), resource(hmUrl), directory(hmUrl, {all}). The embedding Seed window answers with the viewer's client, so an app sees exactly what the viewer can see. Reads only in this pass; writes will need a consent prompt like browser actions.

    Agent: no tool. The agent writes the HTML under its memory and then does an ordinary write hm://<account>/<path> with options.app naming that folder (or one HTML file). The folder is published as a UnixFS directory in the same publish as the change and the document's app attribute points at it; options.action: "update" + options.app publishes a revision. The result carries a ```seed-app fence (body: the hm:// URL) that the chat renders as the running app. Eric, 2026-10-20: "i dont think that tool is necessary if the app is created and stored using a doc/resource. and all apps should be made this way."

What landed (PR #1262, four commits on main, no #1139 code)


    Daemon backend/hmnet/filemanager.go: GET /ipfs/{cid}/{path...} walks UnixFS directories, types entries by extension, serves a folder as its index.html; the browser gate treats these reads as public like a bare blob. Test TestGetDirectoryEntries.

    Shared and desktop: @shm/shared/document-app (scheme, protocol, client script, message guard, app CSP, platform hook); directoryToIpfsBlobs in @seed-hypermedia/client (codec-correct dag-pb CIDs, the memory blockstore returns raw ones); desktop app-document-apps.ts (privileged scheme, handler backed by the daemon, document-app-open IPC), frame policy and CSP admit seed-app: frames; DocumentAppView in @shm/ui/document-app, embedded by the resource page when not editing; web /hm/api/file/<cid>/<path> passthrough.

    Agents: collectDocumentAppFiles in document-apps.ts (folder or single HTML, dotfiles skipped, 256 files / 16 MiB, index.html required); write hm:// document create/update take options.app, publish the folder beside the change and set the attribute; the write guide ("Apps" section, with the window.seed bridge API) and the Agent Guide tell the agent when and how. Chat: DocumentAppEmbed renders a ```seed-app fence.

    Docs: hypermedia/apps/document-apps.md, metadata row, links from apps and desktop pages.

Verified: Go hmnet and daemon gate tests; vitest client, shared, desktop (full suite 1379); bun agents 230 incl. a service test that publishes a folder as a document, updates it in place, and refuses without the Publish grant; typechecks for desktop, ui, shared, client, web, agents. Not yet eyeballed in the running app.

Why the first live try failed (2026-10-20 afternoon)

Eric asked Clever Hare (gpt-6-astra, grants incl. apps and publish) for a full-screen 2048 app in the dev agents at :3051. The agent never called the (then) apps tool: it wrote ~/memory/apps/2048.html, tested it with execute, and replied with [Play 2048](~/memory/apps/2048.html), a private path nobody can open; the :3051 server was also running the old checkout. Eric's verdict: the tool is unnecessary, apps are documents at hm:// addresses, full stop. So the branch was rebuilt on main without any of #1139's session-preview machinery (no apps tool, no loopback host, no session attachments): the agent publishes an app with the write verb it already uses for every document.

Open

    Eyeball on desktop: open a document with app set, confirm the frame loads over seed-app://, seed.context() answers, localStorage persists across an app update.

    Web embed: hyper.media needs a sandbox origin (or an opaque-origin sandbox="allow-scripts" iframe) before it can run apps; today it shows a note to open the desktop app.

    Bridge writes (comments, document edits) behind a consent prompt.

    Should the web pane (the browser tab UI) also accept seed-app:// URLs, so an app can open "full screen" in a tab? Today the app replaces the document body instead.

Log

    2026-10-20: branch created from the rebased #1139 head; note opened. Same day: #1138 merged, #1139 rebased onto main and retargeted; all four pieces implemented, tested, pushed as draft #1262. Afternoon: first live try showed the agent never calling apps; Eric dropped the tool model; branch rebuilt on main with write + options.app and a chat seed-app fence; ~/Code/Seed moved to it for testing.

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

Unsubscribe anytime