oficina / estilo Public

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

Plain git — no account needed to clone.

Download

Download this file

1# 🤖 estilo
2
3> **Confirmed 2026-08-31.** Paul's ruling: if Docs, Sheets and other
4> Oficina apps are going to share a design system, estilo makes sense and
5> should be started, with rastrillo's `day` theme as its base.
6
7> **Ruled, same day.** Paul, verbatim: *"Let's have estilo just be day
8> for now, but we can add oficina specific changes and components to it
9> as we need to."* This settles what "as its base" above meant: estilo's
10> theme **is** `day`, not derived from it or based on it — identical.
11> See "Theme" below.
12>
13> **Superseded 2026-09-29.** Paul chose Calma: estilo's theme is now the
14> suite's own, not `day` (decisions table, row 8; "Theme" below). The two
15> rulings above are kept as the history of how estilo started.
16
17The CARLOS suite's design-system variant. See `README.md` for what it is.
18
19This file is binding. Where it disagrees with a general skill, this file
20wins.
21
22---
23
24## The one rule
25
26> **Two specs are guesses. Two implementations are facts.**
27
28So: **one app builds a primitive here, and the other adopts it.** Not two
29implementations merged afterwards, and not an interface designed from two
30specs at once.
31
32A document editor's inspector shows text and style properties. A
33spreadsheet's shows cell format and data validation. A shape designed from
34both specs at once risks fitting neither — **the second use is what corrects
35it**, and it can only do that if there is something concrete to correct.
36
37**Until a second adoption, estilo is a home, not an agreed interface.**
38Treating it as an interface earlier buys exactly the ill-fitting abstraction
39that building it here was meant to avoid.
40
41(From ui-guidelines-52 via docs-e6, 2026-08-30. Written down because an
42empty repo invites the opposite.)
43
44---
45
46## Settled decisions
47
48| # | Ruling | Rejected | Who, when |
49|---|---|---|---|
50| 1 | **Shared suite components live here, not in rastrillo's `ui`** | Proposing them to the framework | Paul, 2026-08-30 |
51| 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 |
52| 3 | **Resolved: use released Rastrillo v0.23.0** — §Dependency | Pinning a `main` pseudo-version | This session, 2026-08-30 |
53| 4 | **Estilo's own components are written in the bare-attribute grammar, and estilo defines their styling** — §Vocabulary | `class="rst-*"` (right for an app consuming rastrillo's CSS, wrong for a library shipping its own) | Paul, 2026-08-30 |
54| 5 | **The colour engine stays in rastrillo's `ui`** | Moving `Pair` and allocation here | ui-guidelines-52, 2026-08-30 |
55| 6 | *(Superseded by 8.)* **Estilo's theme is `day`, unchanged — not "derived from" or "based on" it. Divergence is allowed but must be driven by a named need, not a preference** — §Theme | Restyling on the strength of "day as a base"; changing a token because it "would look better" | Paul, 2026-08-31 |
56| 7 | **Private module path is `amadan.net/oficina/estilo.git`**, using authenticated Git and `GOPRIVATE` on every builder; checked consumer `go.sum` records integrity. Preserve this path when discovery support changes. | Public visibility solely to obtain Go metadata; local filesystem replaces; a later cosmetic module rename | Overnight coordinator, 2026-09-07; Claude Fable 5.1 branch review |
57| 8 | **Estilo's theme is Calma, the suite's own — no longer `day`.** Every app's colour sits on one ring of equal OKLCH lightness (`theme/apps.go`); each app is themed with its own accent (`theme.ForApp`). The marks are Calma's (Fanned Oficina mark, app icons with a glow of their own hue) and the suite's ambient effect is a seeded nebula (`sky`), placed at the sign-in, the team's home and the front page, **never inside an app** — §Theme | Keeping `day` "as a base"; one hue per app chosen by eye (Mail and Files would not fit); rim light, cores and motes in the sky | Paul, 2026-09-28/29, after six rounds of design directions: *"Calma is by far the best"*; *"Grain and filaments. No cores, no rim lights. Then go."* |
58
59**Two callers at design time is the bar for *extraction*. It says nothing
60about *destination*.** Both consuming apps were told to propose their
61primitives to the framework, applied the right bar, and got the wrong
62answer, because the first question looked like it settled the second. Keep
63the two apart.
64
65---
66
67## Theme — RULED: Calma
68
69> **Paul, 2026-08-31, verbatim:** *"Let's have estilo just be day for
70> now, but we can add oficina specific changes and components to it as
71> we need to."*
72>
73> **Paul, 2026-09-28/29**, choosing among six rounds of design directions
74> (`~/amadan.net/oficina/design-directions/`, published for review at a
75> temporary site): Calma, *"by far the best"*, with the aurora that became
76> a nebula; then *"Grain and filaments. No cores, no rim lights. Then go."*
77
78`day` was the position estilo held until the suite chose its own look.
79It has now: **estilo's theme is Calma.** The "named need" row 6 asked for
80is Paul's decision to give the suite an identity, recorded as row 8.
81
82What Calma is, and where each part lives:
83
84- **`theme/estilo.css`** — the suite's tokens: a cool off-white page,
85 white cards, one grey text scale, 12px shapes, a long low shadow, and
86 Oficina's own accent. Status tones are `day`'s, unchanged.
87- **`theme/apps.go`** — the ring. Every app has a hue; all share one
88 OKLCH lightness and chroma, so no app is louder than another. The hues
89 are spaced to the eye (34-50°, widest across the muddy yellow-greens),
90 with room for Mail and Files. `theme.ForApp(slug)` is an app's whole
91 theme: estilo.css, then its accent. The ring is a closed set.
92- **`theme/fonts.go`** — Onest, Calma's typeface, served by
93 `theme.FontHandler`; the theme falls back to the system face without it.
94- **`brand/`** — the marks, generated from `icons.json` (hues must equal
95 the ring; `brand_test` checks) in web, iPhone and Mac shapes.
96- **`sky/`** — the ambient nebula: seeded per team; its colour (haze and
97 clouds) an inline placeholder painted in the first frame, its grain and
98 filaments two static files,
99 no script, still for anyone who asks for less motion. **It goes where
100 the suite meets you — the sign-in, the team's home, the front page —
101 and never inside an app.** Inside an app, only the icons carry colour.
102
103**The gates that replaced `TestMatchesDayByteForByte`**
104(`theme/theme_test.go`), and why each exists:
105
106- `TestDeclaresDaysTokenSet` — exactly `day`'s token *names*, so
107 rastrillo's `tokens.css` never meets an undefined `var(--rst-*)`, and
108 a rastrillo upgrade that adds or drops a token still fails here.
109- `TestContrastMeetsWCAG` — every pair rastrillo's own gate checks, in
110 both schemes, for estilo.css **and every app's accent**, through
111 rastrillo's `ui.ContrastRatio` (parsing copied verbatim).
112- `TestAccentGoldens` — each app's accent block against `testdata/`, so
113 no colour moves without a diff that says so.
114
115**Changing a value is still a design decision for Paul**, not a
116session's. Change the ring or a token, regenerate the goldens
117deliberately (`go test ./theme -run Goldens -update`), read the diff, and
118record the reason here. "It would look better" is still not a reason on
119its own; Paul saying so is.
120
121---
122
123## Dependency — RULED, and check it before adding an import
124
125Rastrillo v0.23.0 supplies the theme and layout APIs Estilo needs. The earlier
126wait for a release is resolved; this module pins that released version.
127Use authenticated Git and `GOPRIVATE=amadan.net/oficina/estilo.git` for the
128private Estilo module. Consumer go.sum files record its integrity.
129
130**The two-pins trap.** Estilo pins rastrillo; the apps pin estilo. That is
131the frozen-`tokens.css` problem one layer down — a component library
132carrying stale tokens while its consumers upgrade underneath it. **A
133tokens-drift check ships in the first commit that has code**, not after it
134bites. `rastrillo doctor` mechanises the single-pin case; this is the
135two-pin case and it is ours.
136
137---
138
139## Vocabulary — attributes here, and why that differs from the apps
140
141**Estilo's own components use the bare-attribute grammar**
142(`<div rst-inspector>`, variants in the value matched with `~=`), and
143**estilo ships their styling.**
144
145**Historical context:** Rastrillo v0.23.0 now ships attribute grammar. The
146following paragraphs record the earlier decision, before that release.
147
148**Read this before "correcting" it, because the surrounding conversation has
149said both things this week and the right answer depends on which repo you
150are in.**
151
152The argument for writing classes is: the attribute grammar exists in no
153rastrillo tree — verified again at `origin/main` `7a30456`, spec-only, no
154branch working on it — so a component written in it renders unstyled. **That
155is true for an app leaning on rastrillo's CSS, and false here.** Estilo
156ships its own components *and* their styling, so styling its own
157attribute-grammar components is not interim anything; it is permanently
158estilo's styling. There is nothing to migrate later and no parallel
159implementation to carry.
160
161And the positive reason, which is the stronger one: **estilo is the variant
162system that exists because of this direction.** Birthing it on the spelling
163being replaced is backwards. Estilo is where the new grammar gets proven,
164not where it waits.
165
166**The three-way split, which holds across the suite:**
167
168| Markup | Spelling | Styled by |
169|---|---|---|
170| Estilo's own components | Attribute grammar | Estilo |
171| An app's own markup, using estilo components | Attribute grammar | Estilo |
172| An app's own markup, using rastrillo's vocabulary | **The grammar emitted by the pinned release — attributes in v0.23.0** | rastrillo's `tokens.css` |
173
174The third row is not our choice and not our migration. It changes when
175rastrillo changes.
176
177Do not build on `.rst-page-header::after` — the accent rake line is retired
178in favour of `--rst-header-rule`.
179
180**Provenance, because this reversed within an hour.** docs-e6 first advised
181classes, reasoning correctly about its own app and then applying the
182conclusion to a different repo without re-deriving it. Paul challenged it;
183the correction is his. Recorded with the reasoning rather than the outcome,
184because the outcome alone reads like an error.
185
186## Standards every primitive meets
187
188Inherited from rastrillo's gates, and not negotiable downward:
189
190- **Twelve locales.** A primitive that only reads in English is unfinished.
191- **The script-required line stated per primitive.** Say plainly what works
192 with scripts off and what does not. "Acceptable for a grid, unacceptable
193 for a form" is the test.
194- **24×24 minimum target size**, measured on the smaller axis. Do not lean
195 on 2.5.8's spacing exception — a real hit area is honest, and the
196 exception is an argument you have to keep making.
197- **320px reflow.** Content requiring two-dimensional layout may scroll in
198 both axes; everything else reflows or it is a bug, not an exemption.
199- **Colour never carries meaning alone.** Whatever a colour *means* must
200 survive greyscale.
201- **Contrast arithmetic copied verbatim** from `ui/contrast_test.go`, never
202 reimplemented — two gates disagreeing by a rounding decision is a gate
203 that certifies the wrong thing while passing.
204
205---
206
207## The gallery
208
209**`rastrillo/cmd/dsgen` does NOT generate a gallery for an arbitrary
210module — verified 2026-08-31.** Its public surface is `-out`/`-mount`, and
211`internal/designsystem.Render` hardcodes imports of
212`github.com/carlosframework/rastrillo/ui`. It renders *rastrillo's own*
213vocabulary. Pointed at estilo it would reproduce rastrillo.org's gallery.
214
215An earlier version of this file claimed the opposite, on good authority
216that turned out to be wrong. Recorded rather than quietly deleted, because
217the next person will hear the same claim.
218
219So estilo's gallery is an open question, not a solved one. **Do not build a
220second generator on the strength of that** — the right move is probably to
221ask rastrillo for the seam rather than to fork the tool.
222
223---
224
225## Process
226
227Worktree per session, branch per piece of work, regular pushes,
228squash-merge to `main` only when the work is done, approved, and **CI is
229actually green** — checked, not assumed.
230
231**Every spec and every branch is reviewed by the highest-capability model
232available** before approval or merge, and **the reviewing model is
233recorded** in the branch discussion. A review nobody can attribute is a
234review nobody can check.
235
236**Every branch gets a discussion, and that discussion is the review
237record** — it survives a squash where a PR body does not. **A refusal gets
238surfaced in the discussion, never routed around.**
239
240🤖 / 👨 markers, `Co-Authored-By` trailers, published prompt ledgers.
241
242## Known constraints
243
244Measured facts about Calma's values that bear on the next change.
245
246- **The tightest text pair is faint text on an accent-tinted surface**:
247 4.63:1 for Files in dark, against a 4.5:1 floor. Darkening an accent's
248 soft tint or lightening `--rst-text-faint` in dark goes under first.
249- **The tightest boundary is a control border on the sunk surface**:
250 `--rst-line-strong` on `--rst-surface-2` in light, just over 3:1.
251- **At the accent's lightness some hues cannot hold full chroma in
252 sRGB.** `theme.Hex` takes chroma down until the colour fits, so hue and
253 lightness stay true (Files' light accent carries about 0.09, not 0.15).
254 An app that looks greyer than its neighbours is this, not a bug.
255- **A strict 40° ring always puts one app in olive or lime** at this
256 lightness; that is why the hues are spaced by eye.
257
258`TestContrastMeetsWCAG` measures all of these on every run; the numbers
259here are for the conversation before a change, not instead of the test.
260
261## Work in a worktree, and catch main up first
262
263Take a worktree for the work. Do not work in the shared checkout.
264
265 git -C <repo> fetch origin
266 git -C <repo> worktree add ../<repo>-<task> -b <branch> origin/main
267
268Both halves matter, for different reasons.
269
270**The worktree**, because several sessions share this machine and some
271share this repository, and a checkout has one HEAD, one index and one
272working tree between all of them. A branch switch under somebody else's
273uncommitted work destroys it with nothing on screen to say so, and two
274sessions editing the same paths disagree quietly instead of conflicting
275loudly. A worktree gives the work its own HEAD and its own files, and
276costs a directory.
277
278**From `origin/main`, after a fetch** — not from whatever the checkout
279happens to be sitting on. A branch cut from a stale main is a merge
280conflict scheduled for later, against files somebody else has renamed in
281the meantime. A branch cut from another task's branch is worse: it
282carries that task's commits into yours, where they are reviewed as
283though you wrote them, and the two can only be separated by hand.
284
285Remove it when the work has landed — `git worktree remove` — so that
286`git worktree list` keeps saying something true.
287
288Written down because of 2026-09-17, in `meet`. The shared checkout was
289sitting on one task's branch while holding a *different* task's feature
290uncommitted on disk — some four hundred lines, a new migration among
291them — put there by a session nobody else knew was working in that tree.
292Two other sessions were meanwhile coordinating over the same checkout by
293asking each other not to touch it, which is the arrangement a worktree
294makes unnecessary. Nothing was lost. Nothing about the arrangement would
295have said so if it had been, and by then the same feature existed twice,
296once on each side of a rename, because the two sessions had branched
297from different starting points.
298