# 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. **Instance-local**
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). That is a
ruling about *this* app enrolling them, not about passkeys as such — §8.1.1's
suite path deliberately keeps them, at the home's single relying party.

**[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 — designed and ruled, not yet
   shipped.** The spec's §2 commits to content encrypted in SQLite under a DEK
   held by the running instance, enforced by a test that greps raw SQLite bytes
   and bucket objects for fixture plaintext, and it enumerates the six things
   that *do* leak (§2.1) rather than eliding them.
   Do not describe this instance as encrypting content at rest, and
   do not let this entry be read as a shipped property; it is the position the
   product is being built toward, and the thing that makes the position
   defensible is the leak list, which is honest about what the trade costs.

## 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"
  (`docs.list.empty.title`, no full stop — a heading, not a sentence).
  "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.** It is why `git branch --merged` is banned here, why the
   editor bundle is checked by rebuilding it rather than by trusting it, and
   why the seal, when it lands, is specified as a byte-grep over raw storage
   rather than as a code review. The principle is live; the seal is not built
   yet (see Positioning 3).
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. Today exactly one exists: `Locales =
[]string{"en"}` (`internal/docs/render.go`). The spec's plan for the other
eleven — machine-drafted catalogs, with each shared primitive shipping prose
keys (§10.4) — is a commitment, not a shipped state, and future work should
not read this section as though those catalogs are sitting in the repo.
