Give Activity a genuine ninth colour: --tt-orange
All checks were successful
Deploy / deploy (push) Successful in 24s

Requested directly: "can we make activity orange". The locked eight
palette had already run out at six legible text colours (Watch's blue
was the last one, and it's already a contrast exception); a dithered
swatch alone read as unfinished, and dithering the word itself
rendered as illegible static — tried and reverted this session.

--tt-orange (#ff8000) is defined in base.css right after the locked
eight, clearly marked as a deliberate, acknowledged one-off exception
to "eight colours, no ninth" rather than a secretly-smuggled-in tint.
Same flat text+swatch treatment as every other fastext key. Updated
CLAUDE.md's discipline 1 and DESIGN.md's Locked Eight Rule to name the
exception explicitly, so it reads as a documented decision next time
someone audits the palette, not a bug.
This commit is contained in:
Barely Removable 2026-08-30 09:52:19 -07:00
parent 9fba4f210e
commit 517559a460
5 changed files with 55 additions and 39 deletions

View file

@ -66,7 +66,12 @@ the user's Unraid home server. Server-side project directory is still named
than a variation: than a variation:
1. **No in-between tone.** There are eight colours (the teletext 1. **No in-between tone.** There are eight colours (the teletext
primaries). A mid-grey is an ordered dither of two of them, never a primaries). A mid-grey is an ordered dither of two of them, never a
ninth colour. ninth colour — except `--tt-orange`, the one deliberate, acknowledged
break in `base.css`, added when the fastext nav ran out of legible
colours for its eighth destination (Activity) and dithering the
word itself proved illegible. Treat a *second* ninth colour showing
up as the regression this discipline warns about; this one is a
one-time, explained exception, not a precedent.
2. **No enclosure.** Nothing is surrounded by a border or a box. There is 2. **No enclosure.** Nothing is surrounded by a border or a box. There is
zero `border-radius` in the codebase. Grouping is a coloured field row zero `border-radius` in the codebase. Grouping is a coloured field row
(`.tt-field`), indentation, and weight. This is the single thing the (`.tt-field`), indentation, and weight. This is the single thing the
@ -106,23 +111,27 @@ the user's Unraid home server. Server-side project directory is still named
other nav, so it needs no scroll to reach. The index overrides it other nav, so it needs no scroll to reach. The index overrides it
with `HOME_FASTEXT`, the original four-entry row, as its own closing with `HOME_FASTEXT`, the original four-entry row, as its own closing
line, since its body already lists every destination once. Every line, since its body already lists every destination once. Every
destination gets its own distinct mark. Six get a flat colour, text destination gets its own distinct flat colour, text and swatch
and swatch both: Index/Library/Discover/System keep their `r`/`g`/ both: Index/Library/Discover/System keep their `r`/`g`/`y`/`c`
`y`/`c` shortcut; Requests gets magenta/`--pending` with no shortcut; Requests gets magenta/`--pending` with no shortcut (a
shortcut (a deliberate fit — Requests already is "waiting on a deliberate fit — Requests already is "waiting on a human"
human" everywhere else magenta appears); Watch gets blue (`.k-blue`) everywhere else magenta appears); Watch gets blue (`.k-blue`) with
with no shortcut. Blue is a known exception to the app's own 4.5:1 no shortcut. Blue is a known exception to the app's own 4.5:1
text-contrast bar (it clears ~2.4:1 on black) — kept anyway, at the text-contrast bar (it clears ~2.4:1 on black) — kept anyway, at the
visitor's request, over the alternative of a swatch-only accent visitor's request, over the alternative of a swatch-only accent
that read as unfinished next to six fully-coloured neighbours. that read as unfinished next to its fully-coloured neighbours.
Activity gets an ordered-dither swatch (`.k-dither`, same mechanism Activity gets `--tt-orange` (`.k-orange`) — a genuine ninth colour,
as `--dither-mid`) instead of a flat colour — the one other legal the one destination that breaks "eight colours, no ninth"
way to get a distinct mark without a ninth hue. Settings alone outright, defined and explained in `base.css` right after the
keeps plain ink with no swatch treatment — deliberately, as the one locked eight. It's there because the palette ran out at six
page with no live status of its own. Whichever entry matches the (dithering a swatch alone read as unfinished, and dithering the
current page renders as a `.fastext-here` span instead of a link — word itself renders illegible) and the visitor wanted every
a page linking to itself did nothing, so it isn't offered as a destination to look different, not merely as different as eight
click. colours allow. Settings alone keeps plain ink — deliberately, as
the one page with no live status of its own. Whichever entry
matches the current page renders as a `.fastext-here` span instead
of a link — a page linking to itself did nothing, so it isn't
offered as a click.
- **Nothing is clipped, hidden, or sticky.** All seven page numbers and - **Nothing is clipped, hidden, or sticky.** All seven page numbers and
every fastext entry stay visible at every width, reflowing to another every fastext entry stay visible at every width, reflowing to another
row rather than scrolling, truncating, or folding into a menu. No row rather than scrolling, truncating, or folding into a menu. No

View file

@ -181,7 +181,7 @@ Teletext had exactly eight colours, at full saturation, and the product's semant
### Named Rules ### Named Rules
**The Locked Eight Rule.** Eight colours exist. Adding a ninth hue, or desaturating one of the eight to make it sit more politely, is a regression rather than a variation. If a new state needs a colour, it takes one of the eight and inherits that colour's meaning. **The Locked Eight Rule.** Eight colours exist. Adding a ninth hue, or desaturating one of the eight to make it sit more politely, is a regression rather than a variation. If a new state needs a colour, it takes one of the eight and inherits that colour's meaning. **One acknowledged exception:** `--tt-orange` in `base.css`, added for the fastext nav's eighth destination once the locked eight ran out of colours legible as text on black — a one-time, explained break, not a precedent for a second.
**The Dither Rule.** A mid-tone is an ordered dither of two of the eight at cell scale — a checkerboard or a repeating gradient stepped at `--dither-size: 2px` or at `--bar-cell-w` — exactly how a 16-colour screen faked a gradient. It is never a ninth colour smuggled into the palette. The two derived greys are the sanctioned exception, and they derive from white by `color-mix`, not by hand. **The Dither Rule.** A mid-tone is an ordered dither of two of the eight at cell scale — a checkerboard or a repeating gradient stepped at `--dither-size: 2px` or at `--bar-cell-w` — exactly how a 16-colour screen faked a gradient. It is never a ninth colour smuggled into the palette. The two derived greys are the sanctioned exception, and they derive from white by `color-mix`, not by hand.

32
app.py
View file

@ -1304,20 +1304,22 @@ def service_urls():
# Every destination, for the top of every page but the index, each # Every destination, for the top of every page but the index, each
# with its own distinct treatment. Order matches nav_items. Six of the # with its own distinct colour. Order matches nav_items. The four
# eight get a flat colour, text and swatch both: the four with a real # with a real keyboard shortcut (r/g/y/c) keep their original colours;
# keyboard shortcut (r/g/y/c), plus magenta for Requests (the same # Requests gets magenta (the same --pending magenta a request already
# --pending magenta a request already shows once approved elsewhere — # shows once approved elsewhere — Requests IS "waiting on a human");
# Requests IS "waiting on a human") and blue for Watch. Blue is ~2.4:1 # Watch gets blue, ~2.4:1 contrast on black versus the 4.5:1 every
# contrast on black, well under the 4.5:1 every other body text here # other body text here clears — a known, deliberate exception, kept
# clears — a known, deliberate exception, not an oversight; it's set # anyway because a swatch-only accent read as unfinished next to six
# this way because coloured-icon-only for Watch read as unfinished # fully-coloured neighbours. That's every colour the locked eight
# next to six fully-coloured neighbours. Activity gets an ordered # palette has to give (black's invisible on black); Activity's colour,
# dither swatch instead of a flat colour, the one other legal way to # --tt-orange, is a genuine ninth on top of it — a dithered swatch
# get a distinct mark out of this palette without smuggling in a ninth # alone read as unfinished, and dithering the word itself renders
# hue. Settings alone keeps plain ink — deliberately, as the one page # illegible, so it's a deliberate, acknowledged break from "eight
# with no live status of its own, so it's the one that gets no colour # colours, no ninth" rather than a fix that stays inside it. Settings
# rather than an arbitrary leftover. # alone keeps plain ink — deliberately, as the one page with no live
# status of its own, so it's the one that gets no colour rather than
# an arbitrary leftover.
FULL_FASTEXT = [ FULL_FASTEXT = [
("k-red", "r", "100", "Index", "/"), ("k-red", "r", "100", "Index", "/"),
("k-blue", "", "101", "Watch", "/watch"), ("k-blue", "", "101", "Watch", "/watch"),
@ -1325,7 +1327,7 @@ FULL_FASTEXT = [
("k-yellow", "y", "103", "Discover", "/discover"), ("k-yellow", "y", "103", "Discover", "/discover"),
("k-magenta", "", "104", "Requests", "/requests"), ("k-magenta", "", "104", "Requests", "/requests"),
("k-cyan", "c", "105", "System", "/system"), ("k-cyan", "c", "105", "System", "/system"),
("k-dither", "", "106", "Activity", "/activity"), ("k-orange", "", "106", "Activity", "/activity"),
("", "", "900", "Settings", "/settings"), ("", "", "900", "Settings", "/settings"),
] ]

View file

@ -61,6 +61,14 @@
--tt-cyan: #00ffff; --tt-cyan: #00ffff;
--tt-white: #ffffff; --tt-white: #ffffff;
/* A deliberate, acknowledged ninth: the fastext nav has eight
destinations and only five of the locked eight read as legible
text on black once red/green/yellow/cyan/magenta/blue are already
spoken for (Activity ran out — a dithered swatch alone read as
unfinished, and dithering the word itself was tried and found
illegible). Not "one of the eight" — used nowhere else. */
--tt-orange: #ff8000;
/* ── Roles ───────────────────────────────────────────── /* ── Roles ─────────────────────────────────────────────
Each colour means one thing across all nine pages. */ Each colour means one thing across all nine pages. */
--ink: var(--tt-white); /* body text */ --ink: var(--tt-white); /* body text */

View file

@ -533,9 +533,7 @@ a.tt-row:focus-visible .tt-row-sub { color: var(--tt-black); }
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* The key's identity is carried by a cell at its head — the physical /* The key's identity is carried by a cell at its head — the physical
coloured button on the remote. Solid for most; k-blue and k-dither coloured button on the remote. */
override its background below, since a flat swatch can't carry
every one of the eight destinations without repeating a colour. */
.fastext a > span, .fastext-here > span { .fastext a > span, .fastext-here > span {
min-width: 0; min-width: 0;
overflow: hidden; overflow: hidden;
@ -564,10 +562,9 @@ a.tt-row:focus-visible .tt-row-sub { color: var(--tt-black); }
other body text in this app clears — but the word carries the other body text in this app clears — but the word carries the
colour like every other key here, at the visitor's request. */ colour like every other key here, at the visitor's request. */
.fastext .k-blue { color: var(--field); } .fastext .k-blue { color: var(--field); }
/* The one other legal way to get a distinct mark out of eight colours /* --tt-orange is a genuine ninth colour, not one of the locked eight —
without a ninth: an ordered dither, same mechanism as --dither-mid, see its definition in base.css for why the palette ran out here. */
in place of a flat swatch. */ .fastext .k-orange { color: var(--tt-orange); }
.fastext .k-dither::before { background: var(--dither-mid); }
.fastext a:hover, .fastext a:focus-visible { .fastext a:hover, .fastext a:focus-visible {
background: currentColor; background: currentColor;
outline: none; outline: none;