Wiki · §C

Architecture

How Sandpaper is designed — the one invariant, the data flow, the modules, and one turn end to end. The exhaustive detail is one link away in the engineering spec.

← §A Overview · §B Product · §C Architecture · §D Rationale →

C.1 The invariant

The one rule

The HTML file on disk is the document. The server serves it, the selected Claude Code or Codex adapter edits it, direct operations splice it, and the watcher reloads it. Server hashes decide whether a turn really saved bytes; no stdout becomes document content. engg →

C.2 Data flow

browser · authenticated client→ POST /turn →global lifecycle→ registry →Claude | Codex→ write →doc on disk
hash + snapshot + fs.watch → page/client-scoped SSE → reload from disk

Outbound and inbound are separate paths over one local server. A turn writes the file; the watcher pushes the reload. Status rides the same SSE channel as a typed event stream. engg §02 →

C.3 The modules

Provider registry, adapters, state stores, server, toolbar, and tests form one server-owned global lifecycle with zero runtime dependencies.

ModuleStateResponsibility
bin/cli.jsverifiedValidate the arg, start the server, log the URL.
src/server.jsverifiedAuthenticated API, lifecycle, hashes/snapshots, undo, SSE, watcher and scoped reload.
src/path-policy.jsverifiedShared serve, mutation, and doctor repository boundary.
provider registry + adaptersverifiedDiagnose and dispatch claude -p or controlled codex exec; normalize provider frames and scoped resume IDs.
public/sp-client.js + toolbar.*verifiedAuthenticated transport, explicit provider selection/default/reset, separate transcripts, truthful status/undo, Sand, Hands, rollback and accessibility.
test/verifiedNode integration and Chromium browser suites with a deterministic fake runner.

Component map → · authoritative build status → engg §06

C.4 Anatomy of one turn

  1. Click , click a section (it resolves the nearest data-cid), type a change.
  2. The client POSTs authenticated JSON with an explicit provider; the server validates request, provider readiness and page before reservation, then snapshots.
  3. The selected adapter emits normalized provider-tagged frames; the server owns IDs, status, hashes and terminal state.
  4. The provider writes the file; the server compares bytes and reports Saved/undo only for a real selected-document change.
  5. fs.watch routes page-scoped reloads, preserving only the initiating tab's confirmed optimistic direct edit.

Full 10-step sequence → engg §04

C.5 Stream → status

EventStateChip
system/initinitStarting…
message_startthinkingComposing the change…
tool_use · EditeditingEditing <doc>
result · success + changed hashdoneSaved · Undo
result · success + unchanged hashdoneReplied

Full event map → engg §03

C.6 Security

A local-only tool, scoped tight: bound to 127.0.0.1; per-process token plus same-origin/loopback checks on mutations and SSE; shared path policy denies secrets, runtime state, traversal, and escaping symlinks; request bodies are capped. Provider processes still have directory-level write ability. Codex's controlled flags are not OS isolation, and external-path detection is best-effort only.

Threat model → engg §05 · why local-only → §D rationale