Derive the interface palette instead of hardcoding eleven colours
main.rs held eleven `const … : u32` colours, and spikes/floem-shell held a byte-identical copy of the same eleven. Every one was a judgement call made once, and no user could change any of them without recompiling. lumbridge-theme takes a syntax theme's five anchors — background, foreground, comment, and the git added/deleted/modified colours where the theme has them — and derives the whole role set. The frame is the editor background pushed one logarithmic contrast step away from the content, so the work surface is the brightest thing on screen; a theme already at black lifts its surface instead of sinking its frame, which is why a pitch-black theme still shows a seam. Adapted from Buzz's adaptive-theme.ts (block/buzz, Apache-2.0) as a specification, not as copied code. The golden vectors were taken by running the original under Node — a research pass had supplied Python-derived vectors and claimed they reproduced it byte-exactly, and they did not: Python rounds half-to-even, JavaScript rounds half-up, they disagree on exactly one channel value of 22.5, and that decides whether the luminance bisection converges a step early. github-dark's chrome is #171a1d, not #191c20. Provenance colours are separate roles from state colours, with a test holding them pairwise distinct in every theme, because decision 0012 colours a usage reading by where its number came from and never by how alarming it is. This changed no pixels, and that was verified rather than asserted: the only difference between before-and-after screenshots is the digits of a process ID. The check earned its keep — the mechanical rename had rewritten three user-facing strings, turning the sidebar's "ATTENTION · 0" into "theme.attention · 0" and "+ ADD PANEL" into "+ ADD theme.surface". A literal-by-literal diff now confirms zero strings changed. The default theme pins its roles to the previous constants to make that true; the anchors underneath are real, and a test bounds how far the pure derivation sits from them. The terminal ANSI palette keeps its own table, so 29 colour literals remain in main.rs, all terminal. The catalog, its attribution, and the picker are separate work. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2e349282c8
commit
1556b87f37
+19
-1
@@ -27,6 +27,7 @@ messages should be real from the beginning.
|
||||
- `lumbridge-runtime`: session ownership, supervision, recovery, IPC server.
|
||||
- `lumbridge-pty`: portable PTY and process-tree adapters.
|
||||
- `lumbridge-terminal`: VT parsing, scrollback, selection, search, render model.
|
||||
- `lumbridge-theme`: derives the interface palette from a syntax theme's anchors.
|
||||
- `lumbridge-acp`: ACP client, capability negotiation, transcript normalization.
|
||||
- `lumbridge-harness`: manifests, launch profiles, hooks, PTY fallback adapters,
|
||||
and documented status/usage probes.
|
||||
@@ -43,7 +44,7 @@ messages should be real from the beginning.
|
||||
|
||||
The scaffold currently contains `lumbridge-core`, `lumbridge-storage`,
|
||||
`lumbridge-buzz`, `lumbridge-pty`, `lumbridge-runtime`, `lumbridge-terminal`,
|
||||
`lumbridge-harness`, `lumbridge-ui-fixture`, and the `lumbridge` application
|
||||
`lumbridge-harness`, `lumbridge-theme`, `lumbridge-ui-fixture`, and the `lumbridge` application
|
||||
itself. The GPUI shell was a spike in an excluded workspace until it graduated
|
||||
into `apps/lumbridge`; `scripts/ci.sh` now takes `--headless` and `--ui` so the
|
||||
non-UI crates still build in seconds, and `cargo deny check licenses` gates the
|
||||
@@ -207,6 +208,23 @@ typed command plans. Suggestions have no authority. Plan execution is routed
|
||||
through the same command plane as human actions, and trace export to a hosted
|
||||
model requires explicit scope and destination consent. See decision 0007.
|
||||
|
||||
## Theme model
|
||||
|
||||
Every colour in the interface is a named semantic role derived from a syntax
|
||||
theme's five anchors, not a constant. `lumbridge-theme` holds the derivation and
|
||||
no framework types, so the arithmetic is tested headless; the shell converts a
|
||||
derived palette into GPUI colours once per theme change and owns the result.
|
||||
|
||||
The frame is the editor background pushed one logarithmic contrast step away
|
||||
from the content, so the work surface is the brightest thing on screen; a theme
|
||||
already at black lifts its surface instead. Provenance colours are separate
|
||||
roles from state colours, with a test holding them pairwise distinct, because
|
||||
decision 0012 colours a usage reading by its source and never by its value.
|
||||
|
||||
The default theme pins its roles to the eleven constants that preceded it, so
|
||||
introducing the engine changed no pixels. The terminal ANSI palette and the
|
||||
theme catalog are still fixed tables. See decision 0018.
|
||||
|
||||
## Usage model
|
||||
|
||||
Usage is an append-only observation stream, not a mutable percentage field.
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
# 0018: The interface palette is derived from a syntax theme's anchors
|
||||
|
||||
Status: accepted; the default theme reproduces the previous appearance exactly.
|
||||
|
||||
Lumbridge had eleven `const … : u32` colours in `main.rs` and a byte-identical
|
||||
copy of the same eleven in the Floem spike. Every one was a judgement call
|
||||
someone made once, and there was no way for a user to change any of them without
|
||||
recompiling.
|
||||
|
||||
## The approach
|
||||
|
||||
A syntax theme has already answered the hard question — which background,
|
||||
foreground, and comment colour work together — so the interface derives itself
|
||||
from that answer instead of being invented beside it. Five anchors go in
|
||||
(`bg`, `fg`, `comment`, and the git added/deleted/modified colours when the theme
|
||||
has them) and a full role set comes out.
|
||||
|
||||
The core of it: the application frame is the editor background pushed one
|
||||
contrast step *away* from the content, so the frame recedes and the work surface
|
||||
is the brightest thing on screen. The step is logarithmic rather than fixed,
|
||||
because a separation that reads clearly against near-black is invisible against
|
||||
white. A theme already at black has no room below it, so the frame pins to black
|
||||
and the *surface* lifts instead — which is why a pitch-black theme still shows a
|
||||
seam between panel and frame.
|
||||
|
||||
## Adapted from
|
||||
|
||||
Buzz's `desktop/src/shared/theme/adaptive-theme.ts` (block/buzz, Apache-2.0,
|
||||
Copyright 2026 Block, Inc.), pinned in `docs/RESEARCH_SNAPSHOTS.md`. It is itself
|
||||
a port of an earlier `builderbot` original, which is not in `Research/` and whose
|
||||
licence has not been verified; Block's Apache-2.0 grant covers what Block
|
||||
distributes, which is what was read here.
|
||||
|
||||
No implementation code was copied. The algorithm was read as a specification and
|
||||
reimplemented in Rust, and the golden vectors in `derive.rs` are what hold the
|
||||
two together.
|
||||
|
||||
**Those vectors were taken by running the original under Node, not by
|
||||
re-deriving them.** That distinction is not pedantry. A re-implementation in
|
||||
Python reports `#191c20` for github-dark's chrome; the real answer is `#171a1d`.
|
||||
Python's `round` is banker's rounding and JavaScript's `Math.round` is half-up,
|
||||
they disagree on exactly one channel value — 22.5 — and that one channel decides
|
||||
whether the bisection's convergence test trips a step early. The first draft of
|
||||
this work took the Python number on trust from a research pass that claimed to
|
||||
have "reproduced it byte-exactly", and it was wrong. Anything claiming to
|
||||
reproduce these must run the original.
|
||||
|
||||
Two consequences for the port: the arithmetic is `f64` throughout, because
|
||||
JavaScript numbers are `f64` and `f32` drifts the search; and `mix` quantises to
|
||||
eight bits on *every* call, because the bisection searches over the space that
|
||||
rounding produces.
|
||||
|
||||
## The roles
|
||||
|
||||
Named for what they do, not for a container ladder: `surface_active` is the
|
||||
selected panel and the hover fill, not "surface container highest". Nothing in
|
||||
Lumbridge has to reason about how many containers deep it is.
|
||||
|
||||
The five **provenance** colours are separate fields from the state colours, and a
|
||||
test asserts they stay pairwise distinct in every theme. Decision 0012 says a
|
||||
usage value is coloured by where it came from and never by how alarming it is; a
|
||||
theme that collapsed two of them would silently defeat that, and once themes are
|
||||
user-editable the number of ways to do so multiplies.
|
||||
|
||||
## Why the default theme carries overrides
|
||||
|
||||
`RoleOverrides` lets a theme pin a role instead of deriving it. Only
|
||||
`lumbridge-slate` uses it, and only so this change is a **zero-pixel** one: the
|
||||
commit should be reviewable as "colours now come from a palette", with no
|
||||
appearance change smuggled inside it. That was verified by screenshot rather
|
||||
than asserted — the only pixels that differ between the before and after builds
|
||||
are the digits of a process ID, which changes per run.
|
||||
|
||||
The anchors underneath are real, and a test bounds how far the pure derivation
|
||||
lands from the pinned values, so the overrides are a starting position rather
|
||||
than a permanent exemption. A catalog theme setting them would be defeating the
|
||||
engine.
|
||||
|
||||
## Not in this change
|
||||
|
||||
The **terminal ANSI palette** still has its own fixed table, so `main.rs` is not
|
||||
yet free of colour literals — twenty-nine remain, all of them terminal. Per-theme
|
||||
terminal palettes need the extraction pass that arrives with the catalog.
|
||||
|
||||
The **catalog** itself is one theme. The generator, the vendored theme files, and
|
||||
their attribution are a separate piece of work, as is the picker. What lands here
|
||||
is the engine and one first-party theme, which is what the sidebar and settings
|
||||
work needs in order to be built against roles rather than constants.
|
||||
|
||||
The **Floem spike keeps its eleven constants**. It is frozen under decision 0017
|
||||
and is not maintained in parity.
|
||||
Reference in New Issue
Block a user