Merge pull request #132 from fiddlerwoaroof/docs/adr-004-rgb-ruling
CI / Build all bundles (push) Has been cancelled
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Browser JS unit tests (node --test) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Lint the harness crate (push) Has been cancelled
CI / Browser harness (OPFS contract + shell) (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled
CI / Build all bundles (push) Has been cancelled
CI / Test and lint (host) (push) Has been cancelled
CI / wasm32 check (browser bundle crates) (push) Has been cancelled
CI / Browser JS unit tests (node --test) (push) Has been cancelled
CI / Knowledge base consistency (push) Has been cancelled
CI / Lint the harness crate (push) Has been cancelled
CI / Browser harness (OPFS contract + shell) (push) Has been cancelled
CI / Deploy to GitHub Pages (push) Has been cancelled
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
# ADR 004 — Faces: appearance as UI-agnostic data
|
||||
|
||||
**Status:** accepted (2026-09-22) · **Decided by:** the user, who answered all five open
|
||||
questions on PR #122 · **Direction from:** the user (improvise-nn4s) · **Governed by:**
|
||||
`context/design-principles.md` §5
|
||||
questions on PR #122 and then ruled on the one choice they left ·
|
||||
**Direction from:** the user (improvise-nn4s) ·
|
||||
**Governed by:** `context/design-principles.md` §5
|
||||
|
||||
> "I think we want to iterate towards having a way to theme the app like emacs does:
|
||||
> faces, etc. described in a UI-agnostic format that the different frontends can apply
|
||||
@@ -13,11 +14,11 @@ incremental work cannot paint the repo into a corner. It deliberately does *not*
|
||||
finished theming system, and what is still deferred is named at the end.
|
||||
|
||||
It was written with five questions left open on the grounds that they were the user's
|
||||
rather than the ADR's, and **the user answered all five on PR #122**. Their answers are
|
||||
folded into the clauses below and recorded, with the reasoning, in
|
||||
*[What the user decided](#what-the-user-decided)*. Two of them changed the decision rather
|
||||
than merely confirming it: `reverse` is in the portable set, and the file format is no
|
||||
longer deferred.
|
||||
rather than the ADR's, and **the user answered all five on PR #122** — then ruled on the
|
||||
one choice those answers left standing. Their answers are folded into the clauses below
|
||||
and recorded, with the reasoning, in *[What the user decided](#what-the-user-decided)*.
|
||||
Two of them changed the decision rather than merely confirming it: `reverse` is in the
|
||||
portable set, and the file format is no longer deferred.
|
||||
|
||||
## Context
|
||||
|
||||
@@ -182,6 +183,22 @@ two existing `Indexed` greys would keep rendering exactly, and left the fourth a
|
||||
detail". Those three cannot all hold: folding an index into `Rgb` changes what it *means*
|
||||
and breaks the rule in the same stroke. Review caught it; the fourth arm is the fix.
|
||||
|
||||
**Which spelling does what to a terminal user, stated as a table because the difference is
|
||||
invisible in a theme file and the wrong one is a thing to stumble into rather than choose:**
|
||||
|
||||
| spelling | follows the user's terminal theme? | what it is for |
|
||||
|---|---|---|
|
||||
| `Default` | yes — states nothing at all | ordinary text, the terminal's own background |
|
||||
| `Named(slot)` | **yes**, fully: slots 0–15 are what a terminal theme redefines | almost everything; the shipped default theme is written in these |
|
||||
| `Indexed(n)` | in principle, in practice no for 16–255 — nothing re-themes the xterm cube | a shade the sixteen cannot express, such as "just off the background" (the two greys today) |
|
||||
| `Rgb(r,g,b)` | **no** | an exact colour: a brand shade, or a hue outside any palette |
|
||||
|
||||
So a theme author reaching for `Rgb` gets exactly what the user ruled they should be able
|
||||
to have — an exact colour, which is the honest answer for a brand shade or anything the
|
||||
sixteen slots cannot say — **and pays for it by opting that theme's users out of terminal
|
||||
palette tracking.** Fine when chosen, bad when stumbled into, which is why the trade is
|
||||
written here rather than left to be discovered.
|
||||
|
||||
Four things follow, and the first is the point of the whole clause.
|
||||
|
||||
**A named slot is not a colour, it is an index into the viewer's palette.** A theme that
|
||||
@@ -590,16 +607,29 @@ silently ends the user's terminal theme applying to improvise, for 123 of 125 li
|
||||
and the loss would be discovered by a user rather than by a test. Emacs solved this
|
||||
decades ago with named colours plus tty approximation and there is no reason to relearn it.
|
||||
|
||||
**Palette-relative only, no `Rgb` arm at all.** Genuinely tempting, because it makes the
|
||||
regression *unrepresentable* rather than merely defaulted-against — the strongest shape
|
||||
this repo has, and the one `CellId` and `Row<T>` both took. It is rejected on one ground
|
||||
only: a theme author on a truecolor terminal has a legitimate reason to want an exact
|
||||
colour, and a browser theme is naturally written in RGB, so the arm would be reintroduced
|
||||
by the first person who needed it. The protection is therefore a property of the *shipped*
|
||||
theme rather than of the type, which is weaker, and that weakness should be read as a real
|
||||
cost of this clause and not waved at. **If the user would rather have the stronger
|
||||
guarantee, dropping `Rgb` is a coherent version of this ADR** — it costs an exact
|
||||
truecolor theme and buys an invariant.
|
||||
**Palette-relative only, no `Rgb` arm at all — proposed, and REJECTED BY THE USER.**
|
||||
Genuinely tempting, because it makes the regression *unrepresentable* rather than merely
|
||||
defaulted-against, which is the strongest shape this repo has and the one `CellId` and
|
||||
`Row<T>` both took. The draft kept the arm on its own reasoning — a theme author on a
|
||||
truecolor terminal has a legitimate reason to want an exact colour, a browser theme is
|
||||
naturally written in RGB, and the arm would be reintroduced by the first person who needed
|
||||
it — but flagged the weakness and put the choice to the user rather than settling it.
|
||||
|
||||
> **"Keep full color support in themes"** — the user, on `6a4c7dc`
|
||||
|
||||
So `Rgb` stays, by decision rather than by default. **The protection against losing the
|
||||
user's terminal theme is therefore a property of the *shipped theme* and not of the type,
|
||||
and that is now a chosen cost rather than an unexamined one.** It is the honest trade: a
|
||||
theme *may* say anything a display can show, and what keeps the terminal palette alive is
|
||||
that the theme improvise ships declines to.
|
||||
|
||||
**One distinction this ruling does not collapse, stated because it is the easy misreading.**
|
||||
"Full colour support in themes" is about what the *format may express*; the shipped-theme
|
||||
rule is about what *improvise's own default says*. They are independent, and clause 4
|
||||
keeps both: the format admits `Rgb`, and the default theme contains none. If the intent
|
||||
was also that the shipped default use absolute colour, that is a different change and
|
||||
would reopen the regression clause 4 exists to prevent — so it is worth saying out loud
|
||||
rather than inferring.
|
||||
|
||||
**A `Style` struct on the wire instead of a face name.** i.e. the server resolves the
|
||||
theme and projects concrete attributes. Rejected by clause 7: the theme would then have to
|
||||
@@ -617,10 +647,11 @@ document data — precisely the cost that sank improvise-9ix's second serializat
|
||||
|
||||
## What the user decided
|
||||
|
||||
These five were left open on the ground that they were the user's rather than the ADR's,
|
||||
and were answered as inline comments on PR #122 at `246e8f4a`. Each answer is quoted
|
||||
verbatim, with what it settled and where it now lives — the questions are kept because a
|
||||
decision whose alternatives have been deleted reads as though it was never in doubt.
|
||||
The first five were left open on the ground that they were the user's rather than the
|
||||
ADR's, and were answered as inline comments on PR #122 at `246e8f4a`; the sixth is the
|
||||
choice those answers left standing, ruled on at `6a4c7dc`. Each answer is quoted verbatim,
|
||||
with what it settled and where it now lives — the questions are kept because a decision
|
||||
whose alternatives have been deleted reads as though it was never in doubt.
|
||||
|
||||
**1. Is the `item-add` / `category-add` colour split meaningful?** Green for adding an item
|
||||
and yellow for adding a category. If it encodes something ("green means this becomes
|
||||
@@ -705,6 +736,20 @@ the condition is dissolved rather than met and the larger question stays unmade.
|
||||
is **JSON**, which costs no new dependency anywhere. Clause 9 has all of it, including
|
||||
what "editable by hand" constrains.
|
||||
|
||||
**6. Should the `Rgb` arm be dropped?** Not one of the original five: it is the choice the
|
||||
other answers left standing, raised because clause 4's protection against losing a user's
|
||||
terminal theme is a property of the *shipped theme* rather than of the *type*, which is
|
||||
weaker than the unconstructible-bad-state shape this repo usually reaches for.
|
||||
|
||||
> **"Keep full color support in themes"**
|
||||
|
||||
Settled: **the arm stays.** A theme may express any colour a display can show, and what
|
||||
keeps the terminal palette alive is that improvise's own default theme declines to. The
|
||||
alternatives section carries it as a rejected alternative with the reasoning, rather than
|
||||
as an open question — and the weakness is now a **chosen** cost, which is the difference
|
||||
this answer makes. Read narrowly, as clause 4's own note says: this is about what the
|
||||
format may express, not about what the shipped default says.
|
||||
|
||||
## Deferred, with the condition that would settle each
|
||||
|
||||
**The file format is no longer here.** It was deferred on the condition that improvise
|
||||
@@ -728,14 +773,11 @@ the hard part: once `Theme::resolve` exists, a picker is a field and a reload is
|
||||
re-resolve. Building them before the vocabulary lands would fix the interface around an
|
||||
unfinished face set, which is the wrong order.
|
||||
|
||||
**Dropping `Rgb` — an open choice, and the user has not ruled on it.** Clause 4's
|
||||
protection against losing the user's terminal theme is a property of the *shipped theme*
|
||||
(it contains no `Rgb`) rather than of the *type* (which admits one). That is weaker than
|
||||
this repo usually accepts — `CellId`, `Row<T>` and `Checked` all make the bad state
|
||||
unconstructible instead — and the alternatives section records why the arm was kept
|
||||
anyway. It is listed here rather than left in the prose because it is a live choice
|
||||
someone may want to make later, and the cost of making it then rather than now is one
|
||||
enum variant and whatever themes have used it.
|
||||
**Nothing about `Rgb` — the user ruled, and it is not deferred.** The draft left dropping
|
||||
the `Rgb` arm as a live choice; the answer is "keep full colour support in themes", so the
|
||||
arm stays and the alternatives section records it as a rejected alternative with its
|
||||
reasoning rather than an open one. What that fixes permanently is that **clause 4's
|
||||
protection is a property of the shipped theme rather than of the type** — a chosen cost.
|
||||
|
||||
## Consequences
|
||||
|
||||
@@ -752,7 +794,18 @@ them — the ink stops being asserted. And a user can have their own theme witho
|
||||
building a theme *system* first: clause 9's JSON plus the browser's download half is a
|
||||
small feature, and it works because the vocabulary is the hard part and it is done.
|
||||
|
||||
**Bad.** It is indirection where there is currently none: `Color::Yellow` is legible and
|
||||
**Bad, and this one first because it is the clause most carefully argued.** With `Rgb` in
|
||||
the vocabulary by decision, **a user's terminal theme is protected by what improvise
|
||||
ships, not by what the type permits.** A theme may state an absolute colour, and one that
|
||||
does will not track the terminal's palette — so the guarantee is "the default theme does
|
||||
not do this" and not "this cannot happen". That is weaker than this repo's usual bar,
|
||||
where `CellId`, `Row<T>` and `Checked` all make the bad state unconstructible, and it was
|
||||
said plainly before the ruling and is worth saying just as plainly after: **the ruling
|
||||
settles what we ship, not what the type prevents, and those are different guarantees.**
|
||||
What it buys is real — an exact brand colour, or a shade no sixteen-slot palette can
|
||||
express — and clause 4's table says which spelling costs what.
|
||||
|
||||
It is indirection where there is currently none: `Color::Yellow` is legible and
|
||||
`face_style(Face::GridColumnHeader)` requires a lookup to understand. That cost is real and
|
||||
is the honest argument for doing nothing. The answer is that the app already pays it three
|
||||
times (`axis_fg`, `field_kind_fg`, `active_color`) and nobody has objected — the indirection
|
||||
|
||||
Reference in New Issue
Block a user