Octonode Playbook

Design documents and hosting architecture

Canonical Octonode repository documentation.

Design documents and hosting architecture

Status: implemented foundation (2026-07-31) Owners: schema, cloud control plane, Studio

Product contract

Design Docs is a collaborative document surface for technical decisions. A document may belong to the active workspace or to one project, is edited as Markdown, HTML, or plain text, and is previewed as a paged document. Reviewers can pin revision-anchored comments at document coordinates. Authors can embed workflow references or sandboxed HTTPS iframes, publish a revocable unique path as either an open file or a hosted page, and use the browser print pipeline to save the hosted view as PDF.

This is separate from repository documentation. Files under a project remain source-controlled and canonical in the project store. Design Docs is collaboration data: immutable content revisions live in R2 and metadata lives in the workspace social authority. The two surfaces may link to each other, but neither silently copies or overwrites the other.

User surfaces

SurfaceScopePurpose
Studio /studio/design-docsWorkspaceDocuments shared independently of one project
Project Design docs tabProjectDecisions and references for one project
/design/:unique-pathPublic capability pathHosted page or open-file view, according to access mode
@octonode/design-docsStandalone packageStorage keys, embed grammar, safe URL rules, and share paths reusable by another app

The Studio implementation is deliberately a consumer of the package and generated API. A future standalone app can import the same package and call the same HTTP contract without importing Studio, the workflow editor, or server internals.

System shape

flowchart LR
  Author[Studio author] --> API[Generated design-doc API]
  Reviewer[Workspace reviewer] --> API
  Public[Unique public path] --> PublicAPI[Public read-only route]
  API --> Worker[Cloud control Worker]
  PublicAPI --> Worker
  Worker --> Social[Workspace Social Durable Object]
  Worker --> R2[(DESIGN_DOCS R2)]
  Social --> Meta[(Document metadata + access)]
  Social --> Comments[(Existing design review comments)]
  Preview[Document renderer] --> Embed[Sandboxed iframe / workflow reference]

The Worker is the only R2 reader/writer. The bucket is private. A public URL never maps directly to an R2 key: the Worker resolves the workspace and document from the unique slug, asks the workspace Durable Object whether the slug is current and public, and only then reads the referenced object.

Persistence and consistency

Durable Object SQL

design_documents owns scope, access, the current share slug, optimistic version, and update time. Each document also owns a backing design_artifacts row. This lets document comments reuse the existing immutable design target, review thread, reply, resolve, moderation, realtime, backup, and restore machinery rather than creating another comment platform.

design_document_revisions stores title, format, SHA-256, UTF-8 byte size, R2 object key, and the immutable document revision. Its (document_id, document_revision) foreign key points to the backing design artifact revision, so every coordinate pin names the exact bytes that were reviewed.

Workspace documents use @workspace only as their internal comment-project key. That sentinel is not a project authorization shortcut; routes authorize them at workspace scope. Project documents carry the owned project id and repeat the project permission check after resolving document metadata.

R2

Content is stored under:

design-docs/{workspace-kind}/{encoded-workspace-id}/{document-id}/{revision}.{md|html|txt}

Objects are immutable because each save creates a new UUID revision and key. The Worker writes the object first, commits metadata second, and deletes the new object if the metadata transaction fails. Idempotent replays delete an unused duplicate object. Old revisions remain available for anchored comments and recovery; retention cleanup must not delete an object still referenced by metadata.

The DESIGN_DOCS bucket is intentionally separate from plugin bundles, avatars, and social backup archives. Cloudflare's Workers R2 API provides the required private binding, streaming reads, object metadata, and explicit delete operation; no public r2.dev bucket is required.

Revisions and conflicts

Create and save requests require an Idempotency-Key. Save also sends baseRevision. If another writer has appended a revision, the API returns 409 with the current revision; it never overwrites the winning content. Share mutations use the document's integer version for the same reason.

Comments store normalized x/y coordinates and the viewport that produced them. Text comments also store the selected quote plus short prefix/suffix context, while keeping the coordinate as the visible pin. A pin therefore survives responsive rendering of the same revision. Changing document bytes creates a new revision; old pins remain on the old revision and are never silently moved. A later projection feature may offer a best-effort mapping, using the existing detached-anchor state when confidence is insufficient.

Access and unique paths

AccessAuthenticated StudioUnique path
privateCreator and moderator onlyDisabled and revoked
workspaceMembers with design:read; edits need design:reviewDisabled and revoked
public_fileSame authoring rulesOpens Markdown or plain-text source; HTML source is served as text, never active same-origin markup
public_hostSame authoring rulesOpens the standalone document renderer

Switching to a public mode generates a new unguessable slug. Changing or revoking public access invalidates the previous path because the Durable Object stores only the current slug. Public reads return document bytes only. Comments, authoring mutations, member identities, and historical revisions remain authenticated.

The public API path is the only dynamic exception to server API authentication and supports GET only. Cloud Worker routing resolves it before user authentication but still validates the exact slug against the owning Durable Object. Responses use nosniff; raw HTML receives a plain-text content type so a shared document cannot execute on the Studio origin.

Rendering and embeds

The portable embed grammar is intentionally small:

{{workflow:project-id/workflow-id}}
{{iframe:https://reference.example/path}}

Studio inserts workflow references from the current project's workflow selector. For Markdown it also captures the current topology in a portable fenced Mermaid block; preview projects the live reference to an iframe. HTML and text use the same directive, so embeds do not depend on unsafe raw HTML. Ordinary Markdown/HTML is sanitized before insertion. Raw <iframe>, scripts, styles, objects, and srcdoc are removed.

Embed URLs must resolve to HTTPS, except same-origin HTTP during local development. Every iframe has sandbox="allow-scripts allow-forms", lazy loading, no referrer, no same-origin privilege, no popup, download, or top-navigation permission. Workflow embeds use a Studio deep link and still enforce the workflow's own authentication and project permissions. Publishing a document never publishes an embedded private workflow.

This first slice embeds the live workflow view. A future immutable workflow snapshot should be added only when public offline workflow rendering is required; it must store a schema-owned graph snapshot on the document revision rather than granting a public workflow token.

Comments and realtime

Selecting preview text records its quote/context and a normalized position. Add page comment switches the preview into a one-click coordinate mode for visual areas that have no text. Submitting creates an ordinary design review thread targeting the backing artifact and exact document revision. Every local review comment snapshots the authenticated user or agent id and display label, plus its avatar reference, into the existing comment identity columns. The recorded id/label preserves who made the review; the avatar reference follows the user's current profile image. Replies and resolve operations use the existing review endpoints with complete design target context. The workspace social socket broadcasts design.updated; React Query reloads open design-document thread queries so collaborators see new replies and resolution changes without polling.

Anonymous public viewers cannot read or create comments. This keeps workspace discussion and member identity private even when the document itself is public. If public commenting becomes a requirement, it needs a separate abuse, identity, moderation, notification, retention, and rate-limit design.

PDF

Save as PDF invokes the browser's native print dialog. Print CSS removes Studio chrome, the ruler, comment pins, background, borders, and shadows while preserving document layout and embeds as the browser renders them. This is the smallest reliable cross-platform export and works in both Studio preview and the standalone hosted page.

A server-generated binary PDF is intentionally not part of this slice. Add Cloudflare Browser Rendering only when automated, pixel-stable, headless exports or attachment delivery are required; at that point it needs queued jobs, bounded navigation, font policy, iframe timeout behavior, R2 PDF retention, and a signed download route.

API

MethodPathResult
GET/api/design-documents?projectId=List workspace or project documents
POST/api/design-documentsCreate document and first immutable R2 revision
GET/api/design-documents/:idRead metadata and current content
PUT/api/design-documents/:idAppend a revision using baseRevision
POST/api/design-documents/:id/shareSet access and rotate/revoke unique path
GET/POST/api/design-documents/:id/review-threadsList or pin revision comments
GET/api/design-documents/shared/:slugPublic hosted payload
GET/api/design-documents/shared/:slug?raw=1Public raw file payload

All authenticated Studio calls use generated client code through api/design-documents.ts and React Query hooks. The public hosted page uses the generated public GET operation.

Limits and security checklist

  • Content is limited to 2 MiB measured as UTF-8 bytes at both server and Worker boundaries.
  • Titles, ids, formats, scopes, coordinates, viewports, comment bodies, and access values are schema bounded.
  • R2 keys are server generated; clients never supply object keys.
  • Private reads are creator/moderator-only in the Durable Object, not merely hidden in UI.
  • Project documents repeat the project-scoped permission check after metadata resolution.
  • Public routes are exact GET paths; mutation and prefix lookalikes remain protected.
  • Sanitization occurs after Markdown conversion and strips active raw HTML.
  • Iframes receive restrictive sandbox and referrer policies.
  • Unique paths are revocable and never authorize comments or workflow access.
  • R2 failure yields 503; metadata never claims missing bytes as a successful read.

Delivery tasks

  1. T192 — architecture and contracts: document authority, scopes, access, embeds, PDF, and standalone boundary.
  2. T193 — portable package and schema: add @octonode/design-docs, Zod wire contracts, key/embed helpers, and checks.
  3. T194 — R2 and metadata: add the dedicated binding, schema v4 migration, immutable revisions, rollback, and public resolution.
  4. T195 — generated HTTP API: add authenticated CRUD/share/comment routes plus the exact public read route and regenerate.
  5. T196 — Studio and standalone UI: add workspace/project navigation, canvas, editor, hosted page, access control, and stories.
  6. T197 — embeds, comments, and PDF: add workflow/iframe insertion, coordinate pins, replies/resolve, and print stylesheet.
  7. T198 — release verification: generation, build, focused tests, Storybook build, and affected visual screenshot review.
  8. T199 — identified live review: snapshot commenter identity, add text-range anchors and one-click page pins, and refresh threads from realtime review events.

Follow-on work

  • Add revision history selection and detached-anchor projection when reviewers need to compare versions.
  • Add immutable workflow snapshots when a public document must show a workflow without granting workflow access.
  • Add automated binary PDF jobs only when browser print cannot satisfy an actual delivery workflow.
  • Add public comments only with an approved anonymous identity and abuse-control model.

On this page