| 1 | # Product |
| 2 | |
| 3 | <!-- impeccable:product-schema 1 --> |
| 4 | |
| 5 | ## Platform |
| 6 | |
| 7 | web |
| 8 | |
| 9 | ## Users |
| 10 | |
| 11 | **Confirmed by Paul, 2026-09-01: Paul and his collaborators, dogfooding.** |
| 12 | Real use, small N. Design for the people actually in it rather than a |
| 13 | hypothesised market — a feature nobody here uses is not evidence of |
| 14 | anything. |
| 15 | |
| 16 | They are running the CARLOS suite already, so Sheets is not entered cold: |
| 17 | a person arrives from the suite home with a session, and the spreadsheet |
| 18 | is one of several apps on one instance. The job is the ordinary one — |
| 19 | put numbers in a shared grid, have the other person see them — under a |
| 20 | storage rule that is stricter than the ordinary one. |
| 21 | |
| 22 | Two roles exist beyond the team: a **guest** holds no team-wide rights |
| 23 | and sees exactly the sheets granted to them; a **link viewer** is |
| 24 | anonymous, read-only, and mints no membership at all. Both are real |
| 25 | audiences for the read surfaces even though neither is the primary user. |
| 26 | |
| 27 | ## Product Purpose |
| 28 | |
| 29 | A spreadsheet for one team, on that team's own instance, whose contents |
| 30 | are sealed at rest. |
| 31 | |
| 32 | Success is that a team can do its actual spreadsheet work — grid editing, |
| 33 | formulas, live collaboration, import and export — without the storage |
| 34 | layer ever holding a plaintext cell, and without the product lying to |
| 35 | them about what that does and does not protect. |
| 36 | |
| 37 | ## Positioning |
| 38 | |
| 39 | Three claims a neighbouring product could not truthfully copy, all |
| 40 | confirmed binding by Paul on 2026-09-01: |
| 41 | |
| 42 | 1. **Sealed at rest, honestly scoped.** The bucket never holds a |
| 43 | plaintext cell. The instance holds the key while it runs, so a |
| 44 | compromised live host *can* read an open sheet — and the README says |
| 45 | so. **We never claim "we can't read your data."** The UI must never |
| 46 | imply otherwise, in copy, in iconography, or by omission. |
| 47 | 2. **One instance per team.** Sharing is membership across teams: the |
| 48 | sheet never moves, and an outside person becomes a scoped member of the |
| 49 | team that owns it. There is no federation story across teams and there |
| 50 | will not be one. Within a team, Sheets is one of several apps sharing |
| 51 | one trust domain (suite ruling 1), and a same-team sibling may pull |
| 52 | team-visible sheets under the suite contract. |
| 53 | 3. **No affordance is script-required.** Sign-in, the sheet list, sharing |
| 54 | and membership, settings, version history, comments, import and export |
| 55 | all work scriptless, as does a paginated table view of any sheet with |
| 56 | per-cell edit forms. The grid editor is an enhancement on top of a |
| 57 | floor, not the floor. |
| 58 | |
| 59 | ## Operating Context |
| 60 | |
| 61 | - **One binary per instance**, `CGO_ENABLED=0`, cgo-free SQLite, |
| 62 | deployed to CARLOS. Review happens on a deployed canary, never |
| 63 | localhost. |
| 64 | - **The suite home is a separate product in a separate repo.** Sheets |
| 65 | depends on it for identity and consumes a five-call contract; it does |
| 66 | not define it. Passkeys enroll at the home's origin only — instances |
| 67 | never enroll them. |
| 68 | - **Sheets sits beside Docs and the home** under the Oficina suite, which |
| 69 | shares a design system (`estilo`). Chrome that appears in more than one |
| 70 | app is the suite's, not Sheets'. |
| 71 | - Live collaboration is cell-level last-write-wins over SSE. Version |
| 72 | history replays inputs and recomputes; derived values are never logged. |
| 73 | |
| 74 | ## Capabilities and Constraints |
| 75 | |
| 76 | **Built and working as of 2026-09-02:** sign-in, sheet list and creation |
| 77 | with a live preview thumbnail on every row, a grid backed by a Go calc |
| 78 | engine, live sync across tabs over SSE, a scriptless paginated table |
| 79 | view with per-cell editing, XLSX import and export (import by upload or |
| 80 | by dragging a file onto the list; it runs as a background job and lands |
| 81 | on the new sheet), and a contrast notice for imported fills. |
| 82 | |
| 83 | The editing surface is a menu bar (File, Edit, Insert, Format, Data, |
| 84 | Help), a formatting toolbar, a name box and formula bar, and a tab |
| 85 | strip. Cells carry bold, italic, alignment, a number format, a fill and |
| 86 | a text colour; columns and rows can be resized and stay resized. A |
| 87 | colour picked here is stored as hue and chroma with no lightness, so it |
| 88 | follows the reader's light/dark choice; a colour an imported file |
| 89 | authored is painted exactly as the file wrote it. |
| 90 | |
| 91 | **Not reachable yet, though the code is there.** This line previously |
| 92 | read "XLSX/CSV import and export", which was true of the converter and |
| 93 | false of the product. Import stopped being an example of that on |
| 94 | 2026-09-02 and **export stopped being one the same day** — `xlsx.Export` |
| 95 | now has a route and File ▸ Download as .xlsx reaches it. What remains |
| 96 | true is CSV: there is no CSV code in the repo at all, and the line above |
| 97 | does not claim any. |
| 98 | |
| 99 | **Constraints that bind design:** |
| 100 | |
| 101 | - **No build step.** Jspreadsheet CE v5 is vendored as a static asset — |
| 102 | no CDN, no bundler. Its own formula engine is disabled; Go owns calc. |
| 103 | - **300-line cap on any JS module we write**, test-enforced, ratchet-down |
| 104 | only. The vendored grid blob is exempt and named as such. |
| 105 | - **Light/dark is per person, not per document** (confirmed binding, |
| 106 | 2026-09-01), and must resolve before first paint from a cookie read |
| 107 | server-side. Cell fills resolve against the theme, so a late preference |
| 108 | repaints every coloured cell. Never a fetch. |
| 109 | - **Every drag-only affordance has a menu equivalent.** |
| 110 | - **No cell content, sheet title or tab name in any log line, error |
| 111 | string, job status or metric label.** Errors carry coordinates and |
| 112 | codes. This constrains error copy directly. |
| 113 | - Cell values are IEEE doubles end to end; currency is a number format, |
| 114 | not a type. |
| 115 | - Cell fill is stored as OKLCH intent; an imported hex is retained as |
| 116 | authored, even when it fails contrast (see Accessibility). |
| 117 | - Link sharing is viewer-or-commenter, **never editor** — an anonymous |
| 118 | editor would write unattributed rows into version history. |
| 119 | |
| 120 | **Explicitly undecided:** the suite's own domain (the passkey relying |
| 121 | party is provisionally an `oncarlos.com` host). Sheets' own visual world |
| 122 | beyond the suite theme — see Brand Commitments. |
| 123 | |
| 124 | ## Brand Commitments |
| 125 | |
| 126 | - **Name:** Sheets, within the **Oficina** suite (CARLOS). |
| 127 | - **Design system:** `rastrillo` (the framework) with **`estilo`** as the |
| 128 | suite's shared variant. estilo's theme *is* rastrillo's `day` — not |
| 129 | derived from it, identical — until a **named need** says a token has to |
| 130 | differ. "It would look better" is explicitly not such a need; the bar |
| 131 | is "Sheets' grid requires a denser row token that day does not |
| 132 | declare". Any divergence is recorded as a decision, not applied. |
| 133 | - **Markup grammar:** the attribute spelling (`rst-btn="primary compact"`), |
| 134 | never the class spelling. Utilities stay classes. Adopt per screen, |
| 135 | never per element. |
| 136 | - **Voice:** plain, exact, and never reassuring beyond the facts. The |
| 137 | repo's own prose is the reference — it names its residual risks in the |
| 138 | same breath as its guarantees. |
| 139 | - **Authorship is disclosed.** 🤖 / 👨 markers, published prompt and |
| 140 | carbon ledgers. Every path works with AI switched off. |
| 141 | |
| 142 | ## Evidence on Hand |
| 143 | |
| 144 | - `docs/superpowers/specs/2026-08-30-sheets-design.md` — the design spec. |
| 145 | §1 is a dated, attributed settled-decisions table with the rejected |
| 146 | alternative beside each ruling. §2 is the leak list. §14 is the |
| 147 | deferred table, each row carrying a written trigger. |
| 148 | - `AGENTS.md` — binding engineering rules, several of them design-facing. |
| 149 | - A working deployment at `https://sheets.bds.oncarlos.com`. |
| 150 | - `tools/drive/drive.mjs` — drives the real binary in a real browser, |
| 151 | for live sync across two tabs. |
| 152 | - `tools/drive/chrome.mjs` — its sibling, for the editing chrome: the |
| 153 | menu bar, the toolbar, the formula bar, the cell paint, export, and |
| 154 | the 320px reflow budget. It earned its place on the day it was |
| 155 | written, by finding a menu bar that worked perfectly from the keyboard |
| 156 | and not at all with a mouse. |
| 157 | |
| 158 | **Absences future work must not fabricate:** there are no customers, no |
| 159 | testimonials, no benchmarks, no pricing and no usage numbers. There is no |
| 160 | market claim of any kind. Sheets is being dogfooded by its author and his |
| 161 | collaborators, and nothing in the product may suggest more than that. |
| 162 | |
| 163 | ## Product Principles |
| 164 | |
| 165 | 1. **Say the true thing, including the unflattering half.** The trust |
| 166 | model is a genuine differentiator *and* has a named residual risk. Copy |
| 167 | that states the first without the second is a defect, not a |
| 168 | simplification. |
| 169 | 2. **The scriptless path is the product, not the fallback.** If an |
| 170 | affordance exists in the grid and not in the table view, the table view |
| 171 | is unfinished. "Fallback" is the wrong word and invites the wrong |
| 172 | design. |
| 173 | 3. **One instance, one team, no federation.** Never design a flow that |
| 174 | implies data can move to another instance, or that a person outside |
| 175 | this team is a first-class participant. |
| 176 | 4. **The chrome is the suite's; the grid is ours.** Anything that appears |
| 177 | in Docs and the home too belongs to `estilo` and should look identical |
| 178 | there. A spreadsheet's menu bar and formula bar are Sheets'. |
| 179 | 5. **A preference resolves before first paint or it is a bug.** This is a |
| 180 | correctness rule that happens to be about design. |
| 181 | |
| 182 | ## Accessibility & Inclusion |
| 183 | |
| 184 | - **WCAG AA (4.5:1) is the floor for text**, and it is test-enforced |
| 185 | against the theme's own pairs. |
| 186 | - **A retained imported fill may fail that floor, and that is correct.** |
| 187 | Rendering an imported file as authored is the ruling; silently |
| 188 | correcting a colour would lie about the document. The obligation that |
| 189 | follows is to *say so* — a per-cell marker plus a notice — and leave |
| 190 | the decision to the person. Never quietly recolour a cell. |
| 191 | - **The page header's rule is decorative and carries no contrast floor.** |
| 192 | It is deliberately absent from rastrillo's contrast pair table; a later |
| 193 | pass must not argue it up to 1.4.11's 3:1. |
| 194 | - **The theme switcher must work with scripting off** — a person who |
| 195 | needs a high-contrast theme is not well served by being told to enable |
| 196 | JavaScript first. |
| 197 | - Three light/dark states, not two: "follow the machine" is a real |
| 198 | preference. |
| 199 | |