| 1 | # Product |
| 2 | |
| 3 | <!-- impeccable:product-schema 1 --> |
| 4 | |
| 5 | > **How this file was written.** Impeccable's `init` normally interviews the |
| 6 | > user before writing anything here. This file was written overnight, on |
| 7 | > Paul's explicit instruction to proceed unattended, so every fact below was |
| 8 | > derived from repository evidence rather than confirmed in an interview: |
| 9 | > `AGENTS.md`, `docs/superpowers/specs/2026-08-30-docs-design.md` (revision 2), |
| 10 | > the four plans in `docs/superpowers/plans/`, and the shipped code in |
| 11 | > `internal/docs/`. Facts traceable to a spec ruling or to code are cited. |
| 12 | > Anything that is a reading rather than a citation is marked |
| 13 | > **[inferred — confirm]** and should be corrected rather than trusted. |
| 14 | |
| 15 | ## Platform |
| 16 | |
| 17 | web |
| 18 | |
| 19 | ## Users |
| 20 | |
| 21 | **Primary: a member of one team, writing and reading that team's documents.** |
| 22 | The instance boundary *is* the team (spec §1, decision 1) — one deployed |
| 23 | instance per team, not one multi-tenant app with teams inside it. Sharing a |
| 24 | document is therefore membership rather than a link-permission matrix. |
| 25 | |
| 26 | **Secondary: an outside guest**, admitted as a guest member rather than as an |
| 27 | anonymous URL holder (spec §1, decision 1; §8.2). A guest is a real row with a |
| 28 | real identity, which is what lets author colours key off it (§10.2). |
| 29 | |
| 30 | Access today is a magic link, gated by `$DOCS_ALLOWED_ADDRESSES` |
| 31 | (`internal/docs/auth.go`) — a per-instance allowlist. **Instance-local** |
| 32 | passkeys are ruled out on purpose, not deferred: `passkey.Config.Origin` |
| 33 | derives its relying party from the instance's own host, so a person in several |
| 34 | teams would enrol several credentials for one product (spec §8.1). That is a |
| 35 | ruling about *this* app enrolling them, not about passkeys as such — §8.1.1's |
| 36 | suite path deliberately keeps them, at the home's single relying party. |
| 37 | |
| 38 | **[inferred — confirm]** The team is assumed to be small enough that an |
| 39 | allowlist of addresses is an appropriate admission mechanism. Nothing in the |
| 40 | repo states a target team size. |
| 41 | |
| 42 | ## Product Purpose |
| 43 | |
| 44 | CARLOS Docs is a **WYSIWYG, paginated, collaborative document editor** with |
| 45 | DOCX, PDF and Markdown in and out (spec preamble). It is one of three moving |
| 46 | parts in the `oficina` suite, alongside CARLOS Sheets (`../sheets`) and a |
| 47 | shared suite home; Docs is a declared consumer of the home contract (§9) and |
| 48 | of rastrillo's colour engine (§10.2). |
| 49 | |
| 50 | Success is a document that is genuinely *the same document* everywhere it is |
| 51 | seen: same pagination, same layout, same colour, whether it is being edited in |
| 52 | a browser, replayed server-side into HTML for a reader with no JavaScript, or |
| 53 | exported to PDF. |
| 54 | |
| 55 | ## Positioning |
| 56 | |
| 57 | Three things a neighbouring editor could not truthfully copy without rebuilding |
| 58 | its foundations: |
| 59 | |
| 60 | 1. **One wire format, two implementations, fixtures as the contract.** The |
| 61 | browser runs JS `yjs`; the server runs Rust `y-crdt` under wazero. They |
| 62 | agree because checked-in fixtures generated by the JS side must be |
| 63 | reproduced by the Rust side (spec §0). This is what makes the server able |
| 64 | to render, export and thumbnail a document without a headless browser. |
| 65 | 2. **The engine owns page geometry; the browser owns line breaking inside |
| 66 | it** (spec §0, decision 5). Page breaks are ProseMirror decorations, never |
| 67 | nodes (decision 14), so page geometry never enters the CRDT and two |
| 68 | collaborators on different page setups never conflict. |
| 69 | 3. **Sealed at rest, with a stated leak list — designed and ruled, not yet |
| 70 | shipped.** The spec's §2 commits to content encrypted in SQLite under a DEK |
| 71 | held by the running instance, enforced by a test that greps raw SQLite bytes |
| 72 | and bucket objects for fixture plaintext, and it enumerates the six things |
| 73 | that *do* leak (§2.1) rather than eliding them. |
| 74 | Do not describe this instance as encrypting content at rest, and |
| 75 | do not let this entry be read as a shipped property; it is the position the |
| 76 | product is being built toward, and the thing that makes the position |
| 77 | defensible is the leak list, which is honest about what the trade costs. |
| 78 | |
| 79 | ## Operating Context |
| 80 | |
| 81 | - **One Go binary per instance**, `CGO_ENABLED=0`, cross-compiled to |
| 82 | `linux/arm64`, on the CARLOS platform (spec §3). SQLite, Litestream-replicated. |
| 83 | - **Routes shipped today** (`internal/docs/app.go`): `GET /` (document list), |
| 84 | `POST /documents` (create), `GET /d/{sqid}` (read/edit), `GET|POST |
| 85 | /d/{sqid}/updates` (the CRDT update log), plus the sign-in set (`GET|POST |
| 86 | /signin`, `GET /auth/callback`, `GET /auth/verify`, `POST /signout`) and |
| 87 | `/static/*`. Every document route sits behind `RequireSession`. |
| 88 | - **Local development** is `./scripts/run-dev.sh` — `localhost:8099`, a scratch |
| 89 | SQLite database that starts empty every run, magic links logged to the |
| 90 | terminal rather than mailed. |
| 91 | - **The scriptless path is a product requirement, not a fallback** (spec §6.2): |
| 92 | reading a document, the list, search, sharing, settings, and reading/replying |
| 93 | to/resolving comments all work without JavaScript. Editing prose does not, |
| 94 | and that boundary is stated rather than discovered. |
| 95 | - **Work happens on a worktree branch and reaches `main` only by squash merge**, |
| 96 | after a highest-capability-model review, Paul's approval, and green CI |
| 97 | (`AGENTS.md`, Paul's rule 2026-08-30). The review record is an amadan branch |
| 98 | discussion, not a GitHub PR — this repo is hosted on an amadan hub. |
| 99 | |
| 100 | ## Capabilities and Constraints |
| 101 | |
| 102 | **Shipped:** real per-person sign-in with an admission allowlist; the document |
| 103 | list grouped by recency; document creation; server-rendered read view replayed |
| 104 | from the CRDT through wazero; a ProseMirror editor mounted over that same |
| 105 | container; a save queue with retry, a resync controller, and a save-status |
| 106 | indicator. |
| 107 | |
| 108 | **Deliberately not built yet** (spec §14): suggestion mode, footnotes with |
| 109 | tracked-changes import, the version-history timeline, a pageless mode, |
| 110 | full-text search (title and metadata search only — decision 15). |
| 111 | |
| 112 | **Binding technical constraints:** |
| 113 | |
| 114 | - **Markup uses the bare `rst-*` attribute grammar** (`<div rst-inspector>`), |
| 115 | variants matched with `~=`. Unparked by Paul 2026-08-31 once `v0.21.0` |
| 116 | shipped both spellings. Two rules travel with it: **adopt per screen, not per |
| 117 | element**, and **pin `v0.21.0` or later**. Held by |
| 118 | `render_test.go`'s `TestOurTemplatesUseTheAttributeGrammar`. Currently on |
| 119 | `rastrillo v0.22.0`, where the framework itself writes the same spelling. |
| 120 | - **Anything Docs contributes to estilo takes an `estilo-*` prefix, never |
| 121 | `rst-*`** — that namespace is the framework's and a future version may claim |
| 122 | a name we took (spec §10.4). |
| 123 | - **`ui` is rastrillo's vocabulary, not ours.** This app's own selectors are |
| 124 | spelled `[docs-*]` (`[docs-thumb]`, `[docs-canvas]`, `[docs-page]`, |
| 125 | `[docs-text]`, `[docs-save-status]`). |
| 126 | - **Every user-visible string is a catalog key**, resolved through |
| 127 | `rastrillo.T` at render time (`internal/docs/render.go`'s `Catalog`). Twelve |
| 128 | base locales are the target; one (`en`) is declared today. A hardcoded |
| 129 | English string is a bug. Document titles and timestamps are *data*, not |
| 130 | authored copy, and are correctly not keys. |
| 131 | - **Tokens are vendored through `ui.TokensCSS()` / `ui.ThemeCSS(name)`**, never |
| 132 | copied by hand. `rastrillo doctor` mechanises the re-copy step as of `v0.21.0`. |
| 133 | - **A spec ruling, code on `main`, and code in the latest release are three |
| 134 | different things.** Cite `origin/main` after a fetch, or a tag, and say which. |
| 135 | |
| 136 | ## Brand Commitments |
| 137 | |
| 138 | - **Name:** CARLOS Docs, in the `oficina` namespace at |
| 139 | `amadan.net/oficina/docs`, hosted per instance at |
| 140 | `<app>.<sqid>.oncarlos.com` (spec §9.2). |
| 141 | - **The design system is rastrillo's**, themed `day` with the `column` shell |
| 142 | (`internal/docs/render.go`). The suite variant is **estilo** |
| 143 | (`amadan.net/oficina/estilo`), named by Paul. Docs owns three primitives — |
| 144 | **inspector panel**, **presence stack**, **comment thread**; Sheets owns |
| 145 | toolbar groups (§10.4). |
| 146 | - **The canvas is not themed chrome.** A document is a piece of paper and does |
| 147 | not follow the reader's dark-mode preference; the canvas opts out with |
| 148 | `color-scheme: light` and literal colours, never theme tokens (§10.2, spec |
| 149 | decision 9: canvas light by default, dark is a per-person preference, export |
| 150 | always renders true colours). |
| 151 | - **Chrome is a short toolbar plus an inspector on demand** (decision 10) — |
| 152 | explicitly rejecting the dense undifferentiated toolbar, the always-on |
| 153 | inspector, and the floating-only model. |
| 154 | - **The document list is a list with real rendered page-one thumbnails** |
| 155 | (decision 11). Explicitly cut: the template carousel, "Owned by anyone" and |
| 156 | "Last opened by me" filters, grid/list/AZ/folder toggles, and all commercial |
| 157 | and platform chrome. |
| 158 | - **Voice, as evidenced by the shipped catalog:** plain, unhedged, second |
| 159 | person, no exclamation marks, no product cheer. "No documents yet" |
| 160 | (`docs.list.empty.title`, no full stop — a heading, not a sentence). |
| 161 | "Documents you create will show up here, newest first." Error copy states |
| 162 | what happened in the past tense and what helps, and was corrected once |
| 163 | precisely because it described a recovery the person could no longer perform. |
| 164 | |
| 165 | ## Evidence on Hand |
| 166 | |
| 167 | - **Specs:** `docs/superpowers/specs/2026-08-30-docs-design.md` (revision 2, |
| 168 | 1000+ lines, itself the product of a Fable review that returned seventeen |
| 169 | defects against revision 1). |
| 170 | - **Plans:** four, in `docs/superpowers/plans/` — the Rust core and wire |
| 171 | compatibility, the app's first screens, sign-in and samples, editing. |
| 172 | - **Sibling specs:** Sheets at |
| 173 | `docs/superpowers/specs/2026-08-30-sheets-design.md`; the design system at |
| 174 | `2026-08-28-design-system-design.md` in the rastrillo repo. |
| 175 | - **Published guidance:** https://rastrillo.org/docs/design-system (2026-08-31). |
| 176 | - **Seed content:** `internal/docs/seeddata/` and `fixtures/generate-seed.mjs` |
| 177 | produce sample documents for a newly created person's space. |
| 178 | - **What does not exist and must not be invented:** no customers, no |
| 179 | testimonials, no benchmarks, no pricing, no usage numbers, no launch date, |
| 180 | no press. `[docs-thumb]` is reserved space, not a rendered thumbnail — the |
| 181 | real page-one rendering arrives with a later plan's layout engine. |
| 182 | |
| 183 | ## Product Principles |
| 184 | |
| 185 | 1. **State the degradation rather than let it be discovered.** Where something |
| 186 | does not work — scriptless comment creation anchoring to a block, layout |
| 187 | divergence at the line level, the six-item leak list — the product says so |
| 188 | in the place a reader will look, and the spec refuses to claim zero. |
| 189 | 2. **A verification that cannot distinguish "no" from "I can't see it" is not |
| 190 | a verification.** It is why `git branch --merged` is banned here, why the |
| 191 | editor bundle is checked by rebuilding it rather than by trusting it, and |
| 192 | why the seal, when it lands, is specified as a byte-grep over raw storage |
| 193 | rather than as a code review. The principle is live; the seal is not built |
| 194 | yet (see Positioning 3). |
| 195 | 3. **The empty state is a designed screen, not a blank page** (Paul's ruling, |
| 196 | in the catalog's own comments). |
| 197 | 4. **Chrome is quiet so the document is loud.** Everything around the page is |
| 198 | design system; the canvas is the one place that diverges, and it diverges |
| 199 | because paper has different requirements from an admin screen. |
| 200 | 5. **A string a person can read is a catalog key.** Localisation is a |
| 201 | structural property of the app, not a phase. |
| 202 | |
| 203 | ## Accessibility & Inclusion |
| 204 | |
| 205 | **WCAG 2.2 AA, no exemptions** (spec §10.3, restated in `AGENTS.md`). Three |
| 206 | requirements are called out by name because they bind concrete decisions: |
| 207 | |
| 208 | - **24×24 minimum target size.** |
| 209 | - **320px reflow** — the page must work at that width without a horizontal |
| 210 | scroll of the page itself. |
| 211 | - **Colour never carries meaning alone**, which binds the presence stack and |
| 212 | author colours directly: past the colour engine's separation capacity, the |
| 213 | name carries identity (§10.2). |
| 214 | |
| 215 | Twelve base locales are the target set. Today exactly one exists: `Locales = |
| 216 | []string{"en"}` (`internal/docs/render.go`). The spec's plan for the other |
| 217 | eleven — machine-drafted catalogs, with each shared primitive shipping prose |
| 218 | keys (§10.4) — is a commitment, not a shipped state, and future work should |
| 219 | not read this section as though those catalogs are sitting in the repo. |
| 220 | |