Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 62 additions & 24 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,12 @@ python3 tools/ansi_frame_to_image.py frame.ansi frame.html # or .svg
a single tinted pane cannot answer "whose pane is this", and the one that shows the two cues are on
separate channels: identity in hue, focus in luminance. It writes the colours onto the real
`CharacterDefinition`s; the **demo config carries none**, because `PaneTint.None` is the default and a
tinted demo would make every other frame in the gallery show a state most clients are not in),
tinted demo would make every other frame in the gallery show a state most clients are not in) and
`tint-input`/`tint-input-moved` (the same scene with **both command lines up**, before and after a real
⌃→ — the only geometry that can show the *bar* wearing the focused character's colour beside a pane
wearing it, an armed tinted band over an idle tinted one, and the colour travelling with the focus.
The moved frame is also the one that shows a bar wearing a character's hue while its prompt reads
`no connection ›`, which is the composition rule stated in paint: hue says whose, not whether),
`deletions`,
`mssp`/`mssp-none`/`mssp-never` (the **three** states of the F5 ▸ `i` server-information report —
a report, a server that answered and publishes none, and a world nothing has dialled; all three
Expand Down Expand Up @@ -626,34 +631,67 @@ markup (`[bold #rrggbb on #rrggbb]…[/]`, `[[`/`]]` escaping, `[link=url]…[/]
`▌` in the tab *title* — all zero-cost. `FocusIndicationTests.MovingFocusDoesNotMoveAnyPaneRectangle`
is the test that stops this being "improved" into a border. Colours live in `WorkspacePalette`, whose
constants are all derived from a `ScreenPalette` pair so the workspace and the settings screens share
one idea of what focus looks like; the focus step is `CursorBg ÷ EditBg`.
- **A pane's plane says two things, and they are kept on separate channels: identity is *hue*, focus is
one idea of what focus looks like; the focus step is `CursorBg ÷ EditBg`. The **command line** is the
fourth cue and the one that follows you between panes: it wears the armed band when ⏎ sends from it,
and — when the character behind the focused window has chosen a colour — that character's hue as well
(see the tint entry below).
- **A plane says two things, and they are kept on separate channels: identity is *hue*, focus is
*luminance*.** A character may be given a colour (`CharacterDefinition.Tint` → `PaneTint`, Core;
`WorkspacePalette.Tint`, Tui; F5's `tint` row), that colour becomes the pane's base plane, and the same
focus multiplication is applied on top of it — `PaneSurfaceTone` is two lines and the composition is
the whole design. Four things about it are load-bearing. **The tint preserves the surface's luminance
exactly**: the anchor is re-lit to the surface's own luma before it is mixed (`AtLuma`), and luma is
linear in the channels, so the blend is luminance-neutral by construction rather than by a tuned
constant. That is not tidiness — this is the plane the *game's* text is read against, the server picks
that text's colours, and a tint that moved the brightness would change every contrast ratio the theme
was designed around, on a palette we cannot test. It also means the focus step lands the same distance
above a tinted plane as above a plain one, so a colour cannot make one character's pane look more
focused than another's. **It therefore says nothing on a monochrome terminal, deliberately** — the cue
that must survive a lost hue is focus, and the rail and the tab title still name the character in
words. **It is a truecolor cue**: at the surface's luminance the tints are a few points per channel
apart, and a 256-colour terminal quantises them onto the untinted entry — which degrades to exactly the
pane there would otherwise be. That is unacceptable for focus, which is why
the whole design. Load-bearing, in order. **All six tints sit at exactly one luminance**: the plane is
re-lit to the target before the anchor is mixed into it (`AtLuma`), and luma is linear in the channels,
so both ends of the blend share that luma and so does every point between them — by construction, on
any theme, for any `TintStrength`. No character's pane is brighter than another's, and the focus step —
a *multiplication* — therefore lands the same **ratio** above each. **That one luminance is `TintDepth`
below the untinted surface, not level with it**, and the change is deliberate: the first cut held the
tinted plane at the surface's own luma to preserve every contrast ratio the theme was designed around,
and MU\* servers are written for **black** terminals, so the plane their bright ANSI is read on wants
to be darker than the client's chrome rather than level with it. **The depth is bounded and the bound
is arithmetic**: a client may hold tinted and untinted characters at once, so a depth reaching
`1 ÷ FocusScale` would leave a *focused* tinted pane no brighter than an *unfocused* untinted one —
the focus cue reporting the wrong fact. `TintDepth` is the geometric mean of that floor and no
darkening at all, so the untinted surface sits midway in ratio (√FocusScale ≈ 1.26 either way);
`EveryFocusedPaneOutshinesEveryUnfocusedOneAcrossTheWholePalette` is the pin, and it is why "make them
darker still" is a change that has to move `FocusScale` too. **The anchors are saturated for the same
reason**: chroma is bounded by luminance, so at the darker target a muted anchor has nothing left —
the first set's two closest colours were ΔE 8.2 apart and its nearest was ΔE 7.7 from the untinted
plane; these are ΔE 14.4 and ΔE 12.1. **It says nothing on a monochrome terminal, deliberately** — the
cue that must survive a lost hue is focus, and the rail and the tab title still name the character in
words — and it is a **truecolor** cue, which is unacceptable for focus and is why
`FocusSurvivesA256ColourTerminal` exists and has no tint counterpart. And **it costs no cells**, for the
same NAWS reason the focus cue does not: `PaneTintTests.TintingACharacterMovesNoPaneRectangle` is that
pin, and it commits through `SaveConfiguration`, which is also what makes an F5 edit reach the panes
*now* rather than at the user's next focus move. A pane wears the colour of the window **in front of
it** — a pane can host several characters' windows as tabs and paints one rectangle — resolved through
the workspace's ownership record and never through `_active`, because a background pane wearing the
focused character's colour would say the opposite of what it means. The palette is a **closed set of
six names** and not a hex: a free colour cannot be validated against a theme the user may change
tomorrow, and a name survives that change where a hex picked against a dark theme becomes a hole.
`PaneTint.None` is the default and **no migration marks anybody** — the same reasoning as
`ConnectAtStartup`, and the reason the schema version did not move for it.
pin (rectangles *and* `LaidOutRows`, since the command line is a sticky band and one that grew a row
would take that row off every pane), and it commits through `SaveConfiguration`, which is also what
makes an F5 edit reach the panes *now* rather than at the user's next focus move. A pane wears the
colour of the window **in front of it** — a pane can host several characters' windows as tabs and
paints one rectangle — resolved through the workspace's ownership record and never through `_active`,
because a background pane wearing the focused character's colour would say the opposite of what it
means. The palette is a **closed set of six names** and not a hex: a free colour cannot be validated
against a theme the user may change tomorrow, and a name survives that change where a hex picked
against a dark theme becomes a hole. `PaneTint.None` is the default and **no migration marks anybody**
— the same reasoning as `ConnectAtStartup`, and the reason the schema version did not move for it.
- **The command line wears it too, and takes the hue without the depth** (`PaintInputBands`,
`WorkspacePalette.IdleBand(theme, tint)` / `ArmedBand(theme, tint)`). On a pane, luminance carries
focus as a ratio that a step applied equally to all six leaves intact; on the input row luminance is
*already* spoken for — it is the whole armed-versus-idle cue — so a colour that moved it would put a
second fact on a channel that carries one. Hue-only means the armed bar stays exactly the step above
the idle one that it always was, in every colour and on every theme, and that `IdleInk` (measured
against the untinted band, and shared with the tab chips) keeps the contrast it was picked with.
The armed band is derived **from the tinted idle band** rather than tinted itself, so the lean toward
`Theme.Prompt` survives on top of the character's hue and the pair still differ in brightness *and*
colour. The one theme where the step narrows is **Light**, where the lift clamps against white before
the prompt lean is applied — it predates tints, its untinted band is already the narrowest of the
three, and `ATintedCommandLineIsStillObviouslyArmedOrIdle` holds it to `Visible` there and to
`Obvious` everywhere else.
- **Whose colour the bar wears is `SendTarget`'s answer, falling back to the focused window's recorded
owner** (`InputTint`) — never `_active`, which is the misdelivery bug in every shape it has had. The
fallback is the second arm `WindowSession` already walks and the same record `PaneTintOf` reads, so
the bar and the pane above it are *one* answer rather than two that agree most of the time.
**The colour says whose, never whether**: a focused window whose owner has no session this run wears
that owner's colour while the prompt reads `no connection ›` and ⏎ refuses out loud — identity and
reachability are two facts, and the row already states the second twice. A window nobody owns (the
web view) leaves both bands exactly as they were.
- **The Ctrl+arrows move pane *selection*, not keyboard focus — but selection carries the session.** The
pin (`FocusChanged → PinFocusToArmedBar`) is untouched: typing always lands in the armed command line
wherever you have navigated to. That is a fact about which *control* gets a keystroke, and it says
Expand Down
9 changes: 6 additions & 3 deletions docs/design/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,12 @@ top to bottom:
plaintext*), the connect-line template (`connect %CHARACTER% %PASSWORD%`), on-connect, `at start`,
`login` (derived, read-only), session state, log format + folder, `restore`, and `tint` — the colour
this character's panes are painted in, chosen from a closed list of six names (plus `None`, the
default, which leaves the pane on the theme's own surface). A tint changes the plane's hue and not its
brightness, so the game's text keeps every contrast the theme was designed with and the focused pane is
still the brighter one. Right: the trigger-set checklist — each row is
default, which leaves the pane on the theme's own surface). It colours the **command line** as well, so
a glance at the bar says whose connection `⏎` is aimed at. All six tints sit at one luminance — no
character's pane is brighter than another's — one step below the untinted surface, because a MU\*'s own
bright ANSI is read on that plane and the games are written for black terminals; the bar takes the hue
without the step, since brightness there already says which of the two command lines is armed. The
focused pane is still the brighter one, whatever colours are in play. Right: the trigger-set checklist — each row is
`[x] ▪ Comms — channel + page routing 2 rules`. Toggling assigns/unassigns live.

Footer: `[Esc] Close` and, on the right, whatever `⏎` does on the row the cursor is on (`Edit`, `Add`,
Expand Down
Loading
Loading