# 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.