oficina / docs Public

Clone
git clone https://amadan.net/oficina/docs

Plain git — no account needed to clone.

Download

Download this file

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
17web
18
19## Users
20
21**Primary: a member of one team, writing and reading that team's documents.**
22The instance boundary *is* the team (spec §1, decision 1) — one deployed
23instance per team, not one multi-tenant app with teams inside it. Sharing a
24document is therefore membership rather than a link-permission matrix.
25
26**Secondary: an outside guest**, admitted as a guest member rather than as an
27anonymous URL holder (spec §1, decision 1; §8.2). A guest is a real row with a
28real identity, which is what lets author colours key off it (§10.2).
29
30Access today is a magic link, gated by `$DOCS_ALLOWED_ADDRESSES`
31(`internal/docs/auth.go`) — a per-instance allowlist. **Instance-local**
32passkeys are ruled out on purpose, not deferred: `passkey.Config.Origin`
33derives its relying party from the instance's own host, so a person in several
34teams would enrol several credentials for one product (spec §8.1). That is a
35ruling about *this* app enrolling them, not about passkeys as such — §8.1.1's
36suite 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
39allowlist of addresses is an appropriate admission mechanism. Nothing in the
40repo states a target team size.
41
42## Product Purpose
43
44CARLOS Docs is a **WYSIWYG, paginated, collaborative document editor** with
45DOCX, PDF and Markdown in and out (spec preamble). It is one of three moving
46parts in the `oficina` suite, alongside CARLOS Sheets (`../sheets`) and a
47shared suite home; Docs is a declared consumer of the home contract (§9) and
48of rastrillo's colour engine (§10.2).
49
50Success is a document that is genuinely *the same document* everywhere it is
51seen: same pagination, same layout, same colour, whether it is being edited in
52a browser, replayed server-side into HTML for a reader with no JavaScript, or
53exported to PDF.
54
55## Positioning
56
57Three things a neighbouring editor could not truthfully copy without rebuilding
58its foundations:
59
601. **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.
652. **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.
693. **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
103list grouped by recency; document creation; server-rendered read view replayed
104from the CRDT through wazero; a ProseMirror editor mounted over that same
105container; a save queue with retry, a resync controller, and a save-status
106indicator.
107
108**Deliberately not built yet** (spec §14): suggestion mode, footnotes with
109tracked-changes import, the version-history timeline, a pageless mode,
110full-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
1851. **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.
1892. **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).
1953. **The empty state is a designed screen, not a blank page** (Paul's ruling,
196 in the catalog's own comments).
1974. **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.
2005. **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
206requirements 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
215Twelve 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
217eleven — machine-drafted catalogs, with each shared primitive shipping prose
218keys (§10.4) — is a commitment, not a shipped state, and future work should
219not read this section as though those catalogs are sitting in the repo.
220