oficina / docs Public

Impeccable init: PRODUCT.md, derived from the repo rather than an interview

/impeccable init normally interviews before writing this file. Paul
asked for the run to happen overnight, unattended, which the skill's
own autonomy rule treats as permission to infer from the brief instead
— so every fact here comes from repository evidence (AGENTS.md, the
revision-2 design spec, the four plans, and the shipped code under
internal/docs/), each cited where it is traceable to a spec ruling or
to code.

The file says so at the top, and anything that is a reading rather
than a citation is marked "[inferred — confirm]" so it can be
corrected rather than trusted. Two such marks exist today: the assumed
team size behind an address allowlist, and nothing else material.

It records what future design work must not re-derive or contradict:
the instance-per-team boundary, the one-wire-format-two-implementations
position, the engine/browser split on layout, the sealed-at-rest trade
and its stated leak list, the bare rst-* attribute grammar with its
two travelling constraints, the estilo-* prefix rule, catalog keys for
every user-visible string, and WCAG 2.2 AA with the three requirements
the spec calls out by name.

It deliberately records no visual world, palette or typography — init
does not write DESIGN.md, and new-work owns that decision.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QsrkL4m68poXhtf1tGaoek
Paul Campbell pushed by paul@keymail.dev 3d25741103111002d8223b4c81ac3391f8a494fb parent fd1d21a
1 file changed, +205 −0
  • PRODUCT.md +205 −0
diff --git a/PRODUCT.md b/PRODUCT.md
new file mode 100644
index 0000000..18b577a
--- /dev/null
+++ b/PRODUCT.md
@@ -0,0 +1,205 @@
+# Product
+
+<!-- impeccable:product-schema 1 -->
+
+> **How this file was written.** Impeccable's `init` normally interviews the
+> user before writing anything here. This file was written overnight, on
+> Paul's explicit instruction to proceed unattended, so every fact below was
+> derived from repository evidence rather than confirmed in an interview:
+> `AGENTS.md`, `docs/superpowers/specs/2026-08-30-docs-design.md` (revision 2),
+> the four plans in `docs/superpowers/plans/`, and the shipped code in
+> `internal/docs/`. Facts traceable to a spec ruling or to code are cited.
+> Anything that is a reading rather than a citation is marked
+> **[inferred — confirm]** and should be corrected rather than trusted.
+
+## Platform
+
+web
+
+## Users
+
+**Primary: a member of one team, writing and reading that team's documents.**
+The instance boundary *is* the team (spec §1, decision 1) — one deployed
+instance per team, not one multi-tenant app with teams inside it. Sharing a
+document is therefore membership rather than a link-permission matrix.
+
+**Secondary: an outside guest**, admitted as a guest member rather than as an
+anonymous URL holder (spec §1, decision 1; §8.2). A guest is a real row with a
+real identity, which is what lets author colours key off it (§10.2).
+
+Access today is a magic link, gated by `$DOCS_ALLOWED_ADDRESSES`
+(`internal/docs/auth.go`) — a per-instance allowlist. Passkeys are ruled out
+on purpose, not deferred: `passkey.Config.Origin` derives its relying party
+from the instance's own host, so a person in several teams would enrol several
+credentials for one product (spec §8.1).
+
+**[inferred — confirm]** The team is assumed to be small enough that an
+allowlist of addresses is an appropriate admission mechanism. Nothing in the
+repo states a target team size.
+
+## Product Purpose
+
+CARLOS Docs is a **WYSIWYG, paginated, collaborative document editor** with
+DOCX, PDF and Markdown in and out (spec preamble). It is one of three moving
+parts in the `oficina` suite, alongside CARLOS Sheets (`../sheets`) and a
+shared suite home; Docs is a declared consumer of the home contract (§9) and
+of rastrillo's colour engine (§10.2).
+
+Success is a document that is genuinely *the same document* everywhere it is
+seen: same pagination, same layout, same colour, whether it is being edited in
+a browser, replayed server-side into HTML for a reader with no JavaScript, or
+exported to PDF.
+
+## Positioning
+
+Three things a neighbouring editor could not truthfully copy without rebuilding
+its foundations:
+
+1. **One wire format, two implementations, fixtures as the contract.** The
+ browser runs JS `yjs`; the server runs Rust `y-crdt` under wazero. They
+ agree because checked-in fixtures generated by the JS side must be
+ reproduced by the Rust side (spec §0). This is what makes the server able
+ to render, export and thumbnail a document without a headless browser.
+2. **The engine owns page geometry; the browser owns line breaking inside
+ it** (spec §0, decision 5). Page breaks are ProseMirror decorations, never
+ nodes (decision 14), so page geometry never enters the CRDT and two
+ collaborators on different page setups never conflict.
+3. **Sealed at rest, with a stated leak list.** Content is encrypted in SQLite
+ under a DEK held by the running instance; a test greps raw SQLite bytes and
+ bucket objects for fixture plaintext and fails if it finds any (§2). The
+ six things that *do* leak are enumerated rather than elided (§2.1).
+
+## Operating Context
+
+- **One Go binary per instance**, `CGO_ENABLED=0`, cross-compiled to
+ `linux/arm64`, on the CARLOS platform (spec §3). SQLite, Litestream-replicated.
+- **Routes shipped today** (`internal/docs/app.go`): `GET /` (document list),
+ `POST /documents` (create), `GET /d/{sqid}` (read/edit), `GET|POST
+ /d/{sqid}/updates` (the CRDT update log), plus the sign-in set (`GET|POST
+ /signin`, `GET /auth/callback`, `GET /auth/verify`, `POST /signout`) and
+ `/static/*`. Every document route sits behind `RequireSession`.
+- **Local development** is `./scripts/run-dev.sh` — `localhost:8099`, a scratch
+ SQLite database that starts empty every run, magic links logged to the
+ terminal rather than mailed.
+- **The scriptless path is a product requirement, not a fallback** (spec §6.2):
+ reading a document, the list, search, sharing, settings, and reading/replying
+ to/resolving comments all work without JavaScript. Editing prose does not,
+ and that boundary is stated rather than discovered.
+- **Work happens on a worktree branch and reaches `main` only by squash merge**,
+ after a highest-capability-model review, Paul's approval, and green CI
+ (`AGENTS.md`, Paul's rule 2026-08-30). The review record is an amadan branch
+ discussion, not a GitHub PR — this repo is hosted on an amadan hub.
+
+## Capabilities and Constraints
+
+**Shipped:** real per-person sign-in with an admission allowlist; the document
+list grouped by recency; document creation; server-rendered read view replayed
+from the CRDT through wazero; a ProseMirror editor mounted over that same
+container; a save queue with retry, a resync controller, and a save-status
+indicator.
+
+**Deliberately not built yet** (spec §14): suggestion mode, footnotes with
+tracked-changes import, the version-history timeline, a pageless mode,
+full-text search (title and metadata search only — decision 15).
+
+**Binding technical constraints:**
+
+- **Markup uses the bare `rst-*` attribute grammar** (`<div rst-inspector>`),
+ variants matched with `~=`. Unparked by Paul 2026-08-31 once `v0.21.0`
+ shipped both spellings. Two rules travel with it: **adopt per screen, not per
+ element**, and **pin `v0.21.0` or later**. Held by
+ `render_test.go`'s `TestOurTemplatesUseTheAttributeGrammar`. Currently on
+ `rastrillo v0.22.0`, where the framework itself writes the same spelling.
+- **Anything Docs contributes to estilo takes an `estilo-*` prefix, never
+ `rst-*`** — that namespace is the framework's and a future version may claim
+ a name we took (spec §10.4).
+- **`ui` is rastrillo's vocabulary, not ours.** This app's own selectors are
+ spelled `[docs-*]` (`[docs-thumb]`, `[docs-canvas]`, `[docs-page]`,
+ `[docs-text]`, `[docs-save-status]`).
+- **Every user-visible string is a catalog key**, resolved through
+ `rastrillo.T` at render time (`internal/docs/render.go`'s `Catalog`). Twelve
+ base locales are the target; one (`en`) is declared today. A hardcoded
+ English string is a bug. Document titles and timestamps are *data*, not
+ authored copy, and are correctly not keys.
+- **Tokens are vendored through `ui.TokensCSS()` / `ui.ThemeCSS(name)`**, never
+ copied by hand. `rastrillo doctor` mechanises the re-copy step as of `v0.21.0`.
+- **A spec ruling, code on `main`, and code in the latest release are three
+ different things.** Cite `origin/main` after a fetch, or a tag, and say which.
+
+## Brand Commitments
+
+- **Name:** CARLOS Docs, in the `oficina` namespace at
+ `amadan.net/oficina/docs`, hosted per instance at
+ `<app>.<sqid>.oncarlos.com` (spec §9.2).
+- **The design system is rastrillo's**, themed `day` with the `column` shell
+ (`internal/docs/render.go`). The suite variant is **estilo**
+ (`amadan.net/oficina/estilo`), named by Paul. Docs owns three primitives —
+ **inspector panel**, **presence stack**, **comment thread**; Sheets owns
+ toolbar groups (§10.4).
+- **The canvas is not themed chrome.** A document is a piece of paper and does
+ not follow the reader's dark-mode preference; the canvas opts out with
+ `color-scheme: light` and literal colours, never theme tokens (§10.2, spec
+ decision 9: canvas light by default, dark is a per-person preference, export
+ always renders true colours).
+- **Chrome is a short toolbar plus an inspector on demand** (decision 10) —
+ explicitly rejecting the dense undifferentiated toolbar, the always-on
+ inspector, and the floating-only model.
+- **The document list is a list with real rendered page-one thumbnails**
+ (decision 11). Explicitly cut: the template carousel, "Owned by anyone" and
+ "Last opened by me" filters, grid/list/AZ/folder toggles, and all commercial
+ and platform chrome.
+- **Voice, as evidenced by the shipped catalog:** plain, unhedged, second
+ person, no exclamation marks, no product cheer. "No documents yet."
+ "Documents you create will show up here, newest first." Error copy states
+ what happened in the past tense and what helps, and was corrected once
+ precisely because it described a recovery the person could no longer perform.
+
+## Evidence on Hand
+
+- **Specs:** `docs/superpowers/specs/2026-08-30-docs-design.md` (revision 2,
+ 1000+ lines, itself the product of a Fable review that returned seventeen
+ defects against revision 1).
+- **Plans:** four, in `docs/superpowers/plans/` — the Rust core and wire
+ compatibility, the app's first screens, sign-in and samples, editing.
+- **Sibling specs:** Sheets at
+ `docs/superpowers/specs/2026-08-30-sheets-design.md`; the design system at
+ `2026-08-28-design-system-design.md` in the rastrillo repo.
+- **Published guidance:** https://rastrillo.org/docs/design-system (2026-08-31).
+- **Seed content:** `internal/docs/seeddata/` and `fixtures/generate-seed.mjs`
+ produce sample documents for a newly created person's space.
+- **What does not exist and must not be invented:** no customers, no
+ testimonials, no benchmarks, no pricing, no usage numbers, no launch date,
+ no press. `[docs-thumb]` is reserved space, not a rendered thumbnail — the
+ real page-one rendering arrives with a later plan's layout engine.
+
+## Product Principles
+
+1. **State the degradation rather than let it be discovered.** Where something
+ does not work — scriptless comment creation anchoring to a block, layout
+ divergence at the line level, the six-item leak list — the product says so
+ in the place a reader will look, and the spec refuses to claim zero.
+2. **A verification that cannot distinguish "no" from "I can't see it" is not
+ a verification.** This is a shipped habit, not an aspiration: it is why
+ `git branch --merged` is banned here and why the seal is a byte-grep test.
+3. **The empty state is a designed screen, not a blank page** (Paul's ruling,
+ in the catalog's own comments).
+4. **Chrome is quiet so the document is loud.** Everything around the page is
+ design system; the canvas is the one place that diverges, and it diverges
+ because paper has different requirements from an admin screen.
+5. **A string a person can read is a catalog key.** Localisation is a
+ structural property of the app, not a phase.
+
+## Accessibility & Inclusion
+
+**WCAG 2.2 AA, no exemptions** (spec §10.3, restated in `AGENTS.md`). Three
+requirements are called out by name because they bind concrete decisions:
+
+- **24×24 minimum target size.**
+- **320px reflow** — the page must work at that width without a horizontal
+ scroll of the page itself.
+- **Colour never carries meaning alone**, which binds the presence stack and
+ author colours directly: past the colour engine's separation capacity, the
+ name carries identity (§10.2).
+
+Twelve base locales are the target set; the eleven non-English catalogs are
+machine-drafted and the primitives each ship prose keys (§10.4).