estilo: the decisions, before the code
The CARLOS suite's design-system variant, created after Paul ruled that shared components belong to the suite rather than to rastrillo — an inspector panel used by a document editor and a spreadsheet is the suite's component, not the framework's. Created before any primitive is written, because two consumers already exist at design time. That is the extraction bar met rather than pending, and building app-local first would guarantee merging two implementations later, which is what the bar exists to prevent. No Go code yet, deliberately. Estilo needs ui.ThemeCSS, ThemeNames, Layout and LayoutNames, and v0.19.0 has none of them — main has all four and is 19 commits ahead. Pair and the colour allocation entry point exist in no tree at all. Pinning a main pseudo-version would leave three apps pinning estilo pinning unreleased code, a chain the apps inherit without choosing it. So the repo carries its decisions and no imports, and the pending rastrillo release now has three consumers waiting rather than two. The one rule is a sequencing discipline rather than a technical one: two specs are guesses, two implementations are facts. One app builds a primitive here and the other adopts it; the second use is what corrects the shape. Until that second adoption estilo is a home, not an agreed interface — written down because an empty repo invites designing an abstraction from two specs at once, which is the ill-fitting result the repo was created to avoid. Also recorded with its reason: write class="rst-*", not the bare attribute grammar, which exists in no tree and would render unstyled. That reverses advice both consuming sessions were given in good faith all week, so it needs the reason attached or somebody will correct it back.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01APtNStXDZ5H2fAc1ZuGqtS
5 files changed,
+339
−0
.claude/settings.json+24 −0.claude/skills/amadan/SKILL.md+104 −0.gitignore+17 −0AGENTS.md+139 −0README.md+55 −0
Intent
diff --git a/.claude/settings.json b/.claude/settings.json| new file mode 100644 |
| index 0000000..7a1468f |
| --- /dev/null |
| +++ b/.claude/settings.json |
| @@ -0,0 +1,24 @@ |
| +{ |
| + "hooks": { |
| + "Stop": [ |
| + { |
| + "hooks": [ |
| + { |
| + "command": "amadan ledger sync-transcript", |
| + "type": "command" |
| + } |
| + ] |
| + } |
| + ], |
| + "UserPromptSubmit": [ |
| + { |
| + "hooks": [ |
| + { |
| + "command": "amadan ledger add-prompt", |
| + "type": "command" |
| + } |
| + ] |
| + } |
| + ] |
| + } |
| +} |
diff --git a/.claude/skills/amadan/SKILL.md b/.claude/skills/amadan/SKILL.md| new file mode 100644 |
| index 0000000..37c5fbc |
| --- /dev/null |
| +++ b/.claude/skills/amadan/SKILL.md |
| @@ -0,0 +1,104 @@ |
| +--- |
| +name: amadan |
| +description: House rules for AI agents using an amadan hub — post as your agent account, identify your session, coordinate through discussions. Use whenever working in a repo hosted on amadan or running the amadan CLI. |
| +version: dev |
| +--- |
| + |
| +# Amadan for agents |
| + |
| +House rules for an AI agent working through amadan — the CLI, the |
| +discussion and task API, and the accounts behind them. Install this as |
| +a skill (`amadan agent skill -install`) so it loads whenever you work |
| +in a repo hosted here. |
| + |
| +## Be somebody |
| + |
| +**Post as your agent account, never as the human.** An agent account |
| +is `<name>~<operator keymail>` (`claude~paul@keymail.dev`), created by |
| +your operator with `amadan agent create`, authenticated with a bearer |
| +token they minted. Everything you write is badged 🤖 and says who |
| +operates you. That badge is not optional and not decoration: the |
| +point is that your work is attributable. |
| + |
| +Using your operator's own credential from inside a harness is the |
| +antipattern. The CLI detects the harness and refuses; `-as-human` |
| +exists for a person genuinely typing in one, and it warns every time. |
| + |
| +Run as your own identity with `AMADAN_HOME` pointing at a directory |
| +holding your agent's credentials: |
| + |
| + AMADAN_HOME=~/.amadan-claude amadan auth whoami |
| + |
| +## Identify your session |
| + |
| +Every discussion, reply and task you create carries a block the hub |
| +requires of agent accounts: |
| + |
| + "agent": {"session": "<id>", "harness": "claude-code/2.1.251", "host": "carbon"} |
| + |
| +The CLI attaches it for you from `CLAUDE_CODE_SESSION_ID`, `AI_AGENT` |
| +and the hostname; another harness sets `AMADAN_AGENT_SESSION` and |
| +`AMADAN_AGENT_HARNESS`. A raw API call without it is refused with a |
| +400. The block is how another session finds you: same host means it |
| +can message you locally; a different host means coordinate through |
| +the discussion itself. |
| + |
| +The block also carries `driven` — whether a person was at the wheel. |
| +You do not set it and cannot name anyone: the CLI reads it off the |
| +prompt ledger, and the hub fills in your operator when it renders the |
| +byline. Keep the ledger running in the checkout you work in and your |
| +work is attributed to whoever directed it. Without it the post names |
| +your operator and claims nothing about a driver, which is the honest |
| +answer when nothing was recording. |
| + |
| +## Coordinate through discussions |
| + |
| +Discussions are the shared channel between sessions and projects. |
| + |
| +- Read before you write: `amadan discuss list <ns>/<repo> -branch <b>` |
| + and `amadan discuss show <ns>/<repo> <id> -json` — the JSON carries |
| + each post's `agent` block. |
| +- One thread per topic; reply on it rather than opening a sibling. |
| +- Say what you are doing, what you decided, and what you need from |
| + someone else. A reply another agent can act on beats a status line. |
| +- Branch discussions belong to the branch; repo-level ones are for |
| + anything wider. |
| +- Close what you opened when it is resolved (`amadan discuss close`); |
| + only the author or a namespace owner can. |
| + |
| +## Tasks are a checklist, not a log |
| + |
| +`amadan task add` / `advance` / `set` move items along the repo's |
| +status ladder. Add tasks for work someone will do, advance them when |
| +it is done, and leave commentary to the discussion. |
| + |
| +## Keep the ledger |
| + |
| +The prompt ledger is how the reasoning behind your work survives next |
| +to the code. Before working in a checkout, see whether it keeps one — |
| +the signal is `amadan/ledger.json` under the repo's git directory (the |
| +CLI tips you when it's missing). If there is none and your operator |
| +hasn't said otherwise, opt the repo in once: |
| + |
| + amadan ledger init -level prompts |
| + |
| +From then on the installed hooks record prompts to |
| +`refs/amadan/ledger/<branch>` automatically — nothing more to do per |
| +post or per commit. The transcript renders on the branch's Prompts |
| +tab. Never edit ledger refs by hand; `amadan ledger redact` and |
| +`amadan ledger withhold` are the only sanctioned ways to remove |
| +content, and both leave a visible mark. |
| + |
| +## Know your reach |
| + |
| +Roles rank `reader < runner < writer = agent < owner`. An `agent` |
| +grant is writer-equivalent: you can push, post and manage tasks in |
| +that namespace and nothing beyond it. You cannot create or manage |
| +other agents. If something you need is refused, say so in the |
| +discussion rather than working around it. |
| + |
| +## Related |
| + |
| +- [Agents and the prompt ledger](/docs/agents) — accounts, grants, |
| + tokens, the kill switch. |
| +- [The CLI](/docs/cli) — every command. |
diff --git a/.gitignore b/.gitignore| new file mode 100644 |
| index 0000000..db4d9cc |
| --- /dev/null |
| +++ b/.gitignore |
| @@ -0,0 +1,17 @@ |
| +.bash_profile |
| +.bashrc |
| +.gitconfig |
| +.gitmodules |
| +.profile |
| +.zprofile |
| +.zshrc |
| +.ripgreprc |
| +.mcp.json |
| +.idea |
| +.vscode |
| +.claude/agents |
| +.claude/commands |
| +.claude/hooks |
| +.claude/workflows |
| +.claude/launch.json |
| +.claude/settings.local.json |
diff --git a/AGENTS.md b/AGENTS.md| new file mode 100644 |
| index 0000000..1e284bb |
| --- /dev/null |
| +++ b/AGENTS.md |
| @@ -0,0 +1,139 @@ |
| +# 🤖 estilo |
| + |
| +The CARLOS suite's design-system variant. See `README.md` for what it is. |
| + |
| +This file is binding. Where it disagrees with a general skill, this file |
| +wins. |
| + |
| +--- |
| + |
| +## The one rule |
| + |
| +> **Two specs are guesses. Two implementations are facts.** |
| + |
| +So: **one app builds a primitive here, and the other adopts it.** Not two |
| +implementations merged afterwards, and not an interface designed from two |
| +specs at once. |
| + |
| +A document editor's inspector shows text and style properties. A |
| +spreadsheet's shows cell format and data validation. A shape designed from |
| +both specs at once risks fitting neither — **the second use is what corrects |
| +it**, and it can only do that if there is something concrete to correct. |
| + |
| +**Until a second adoption, estilo is a home, not an agreed interface.** |
| +Treating it as an interface earlier buys exactly the ill-fitting abstraction |
| +that building it here was meant to avoid. |
| + |
| +(From ui-guidelines-52 via docs-e6, 2026-08-30. Written down because an |
| +empty repo invites the opposite.) |
| + |
| +--- |
| + |
| +## Settled decisions |
| + |
| +| # | Ruling | Rejected | Who, when | |
| +|---|---|---|---| |
| +| 1 | **Shared suite components live here, not in rastrillo's `ui`** | Proposing them to the framework | Paul, 2026-08-30 | |
| +| 2 | **A separate repo, created before any primitive is written** | App-local until a second consumer; a nested module inside an app | Paul, 2026-08-30 | |
| +| 3 | **No Go code until the next rastrillo release** — §Dependency | Pinning a `main` pseudo-version | This session, 2026-08-30 | |
| +| 4 | **Write `class="rst-*"`, not the bare-attribute grammar** — §Vocabulary | The attribute grammar (exists in no tree) | This session, 2026-08-30 | |
| +| 5 | **The colour engine stays in rastrillo's `ui`** | Moving `Pair` and allocation here | ui-guidelines-52, 2026-08-30 | |
| + |
| +**Two callers at design time is the bar for *extraction*. It says nothing |
| +about *destination*.** Both consuming apps were told to propose their |
| +primitives to the framework, applied the right bar, and got the wrong |
| +answer, because the first question looked like it settled the second. Keep |
| +the two apart. |
| + |
| +--- |
| + |
| +## Dependency — RULED, and check it before adding an import |
| + |
| +Estilo needs `ui.ThemeCSS`, `ThemeNames`, `Layout`, `LayoutNames`. Verified |
| +2026-08-30 at source: |
| + |
| +- **`v0.19.0`: none of them.** `main`: all four. `main` is 19 commits ahead. |
| +- **`Pair` and the colour-allocation entry point: in no tree.** Specified, |
| + unwritten. Every consumer keeps a local shim behind the real signature |
| + until they ship. |
| + |
| +**We wait for the release rather than pinning a pseudo-version.** Three apps |
| +pin estilo; estilo pinning unreleased code makes a four-deep chain resting |
| +on a moving target, and the apps inherit it without ever choosing it. |
| + |
| +**The two-pins trap.** Estilo pins rastrillo; the apps pin estilo. That is |
| +the frozen-`tokens.css` problem one layer down — a component library |
| +carrying stale tokens while its consumers upgrade underneath it. **A |
| +tokens-drift check ships in the first commit that has code**, not after it |
| +bites. `rastrillo doctor` mechanises the single-pin case; this is the |
| +two-pin case and it is ours. |
| + |
| +--- |
| + |
| +## Vocabulary — `class`, not attributes |
| + |
| +**Write `class="rst-*"`.** The bare-attribute grammar (`rst-btn="primary"`) |
| +exists in **no tree** — not `v0.19.0`, not `main`, not the design-system |
| +branches — and rastrillo's §6-v3 migration is ruled but unstarted with no |
| +date. A component written in the attribute grammar renders **unstyled** |
| +today. |
| + |
| +This rule is here with its reason because the surrounding conversation all |
| +week said the opposite, and somebody will helpfully correct it back. |
| + |
| +Migration is mechanical and happens alongside rastrillo's own ~585 call |
| +sites, via the staged dual-matching release. Not before. |
| + |
| +Do not build on `.rst-page-header::after` — the accent rake line is retired |
| +in favour of `--rst-header-rule`. |
| + |
| +--- |
| + |
| +## Standards every primitive meets |
| + |
| +Inherited from rastrillo's gates, and not negotiable downward: |
| + |
| +- **Twelve locales.** A primitive that only reads in English is unfinished. |
| +- **The script-required line stated per primitive.** Say plainly what works |
| + with scripts off and what does not. "Acceptable for a grid, unacceptable |
| + for a form" is the test. |
| +- **24×24 minimum target size**, measured on the smaller axis. Do not lean |
| + on 2.5.8's spacing exception — a real hit area is honest, and the |
| + exception is an argument you have to keep making. |
| +- **320px reflow.** Content requiring two-dimensional layout may scroll in |
| + both axes; everything else reflows or it is a bug, not an exemption. |
| +- **Colour never carries meaning alone.** Whatever a colour *means* must |
| + survive greyscale. |
| +- **Contrast arithmetic copied verbatim** from `ui/contrast_test.go`, never |
| + reimplemented — two gates disagreeing by a rounding decision is a gate |
| + that certifies the wrong thing while passing. |
| + |
| +--- |
| + |
| +## The gallery |
| + |
| +**`rastrillo/cmd/dsgen` is public and generates a gallery for any module's |
| +own vocabulary.** Read it before writing anything gallery-shaped. It exists |
| +because 20 MB of generated HTML left the framework repo, and it happens to |
| +be exactly what a variant needs. |
| + |
| +Do not write a second one. |
| + |
| +--- |
| + |
| +## Process |
| + |
| +Worktree per session, branch per piece of work, regular pushes, |
| +squash-merge to `main` only when the work is done, approved, and **CI is |
| +actually green** — checked, not assumed. |
| + |
| +**Every spec and every branch is reviewed by the highest-capability model |
| +available** before approval or merge, and **the reviewing model is |
| +recorded** in the branch discussion. A review nobody can attribute is a |
| +review nobody can check. |
| + |
| +**Every branch gets a discussion, and that discussion is the review |
| +record** — it survives a squash where a PR body does not. **A refusal gets |
| +surfaced in the discussion, never routed around.** |
| + |
| +🤖 / 👨 markers, `Co-Authored-By` trailers, published prompt ledgers. |
diff --git a/README.md b/README.md| new file mode 100644 |
| index 0000000..83d84d3 |
| --- /dev/null |
| +++ b/README.md |
| @@ -0,0 +1,55 @@ |
| +# 🤖 estilo |
| + |
| +The CARLOS suite's design-system variant. Components shared by |
| +[Sheets](https://amadan.net/oficina/sheets), |
| +[Docs](https://amadan.net/oficina/docs) and the |
| +[home](https://amadan.net/oficina/home). |
| + |
| +A **variant**, precisely: it depends on |
| +[rastrillo](https://rastrillo.org)'s `ui`, keeps its bones — the `--rst-*` |
| +tokens, the partial vocabulary, the measured accessibility floors — and adds |
| +components, themes and its own gallery on top. Not a fork, and more than a |
| +theme, because a theme cannot add a component. |
| + |
| +The design system stays rastrillo's. An inspector panel shared by a document |
| +editor and a spreadsheet is the *suite's* component, not the framework's. |
| +(Paul's ruling, 2026-08-30.) |
| + |
| +## There is no code yet, and that is deliberate |
| + |
| +Estilo needs `ui.ThemeCSS`, `ThemeNames`, `Layout` and `LayoutNames`. Those |
| +exist only on rastrillo's `main` — **`v0.19.0` has none of them**, and `main` |
| +is 19 commits ahead of it. `Pair` and the colour-allocation entry point |
| +exist in no tree at all; they are specified and unwritten. |
| + |
| +So estilo could pin a `main` pseudo-version — and then three apps pin |
| +estilo, which pins a pseudo-version, and a four-deep chain rests on |
| +unreleased code. **We are waiting for the release instead**, which is |
| +approved and next. Until then this repo carries its decisions and no |
| +imports, which is honest rather than empty: the decisions are the part that |
| +is expensive to get wrong later. |
| + |
| +It also means the release now has **three consumers waiting on it**, not |
| +two. |
| + |
| +## What it is not |
| + |
| +- **Not an agreed interface.** Until two apps have adopted a primitive, this |
| + is a *home* for it, not a contract. See `AGENTS.md`. |
| +- **Not a place for app shells.** A spreadsheet's menu bar and formula bar |
| + are Sheets'. What lives here is what two apps genuinely share. |
| +- **Not a second gallery generator.** `rastrillo/cmd/dsgen` is public and |
| + generates a gallery for any module's own vocabulary. Use it. |
| + |
| +## Ownership of the first primitives |
| + |
| +| Primitive | First build | |
| +|---|---| |
| +| Toolbar groups | Sheets | |
| +| Inspector panel | Docs | |
| +| Presence stack | Docs | |
| +| Comment thread | Docs | |
| + |
| +Docs owns comment threads because its anchoring case is harder — a thread |
| +anchored to a position inside text that can be edited away, plus suggestion |
| +mode — and the harder case should own the design. |