From 3df2fd6eb8612adf8cf84046d8706bc50e2c0be2 Mon Sep 17 00:00:00 2001 From: DevMando Date: Wed, 9 Sep 2026 23:51:23 -0700 Subject: [PATCH] Give the token metric a surface instead of a fainter colour MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The per-turn token count was --dim text, right-aligned. That put the smallest and palest element in the app in the exact corner where every themed overlay's vignette is darkest: 2.02:1 on Cathode Ray, 1.95:1 on Pheteven Phosphor, against a 4.5 floor. Raising the colour does not fix it. Full foreground still measured 3.79 and 2.77 on those two, because the vignette was strong enough to defeat any palette value. The distinction matters for the contrast floor added earlier: scanlines attenuate UNIFORMLY, so a raised floor answers them, but a vignette attenuates by position, so the same colour passes mid-pane and fails in the corner. There is no value to pick, and no palette-level rule that helps. So the metric gets a surface. On its own pill it carries its own background and border, reads as a deliberate element rather than trailing text, and survives a busy chat wallpaper — the reasoning the plan-activity card already uses. De-emphasis now comes from size and containment rather than faintness, which is the durable form: small and bounded stays legible under anything painted over it; faint stops being legible the moment something is. Deliberately NOT wrapped in chip-row, despite holding an ordinary chip: IsEphemeralStatus drops every chip-row from a restored session, on the grounds that status pills describe a dead process's live state. A token count is the opposite — it is what that turn cost, and it belongs to history. Reusing that class would have silently deleted these from every reopened session. The vignettes were also simply too strong, tuned by eye against body text which has enormous headroom, and were eating everything fainter — including ordinary text at the pane edges. Eased across all four overlaid themes. --- CHANGELOG.md | 7 +++++ .../Assets/web/transcript/transcript.css | 28 +++++++++++++++---- .../Services/TranscriptHtmlBuilder.cs | 12 +++++++- 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c05aa7e..8d84459 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -209,6 +209,13 @@ for every approved plan. Desktop's version follows the engine generation, so it a sticky header, or the suggestion list a field opens when it is filled. ### Fixed +- **The per-turn token count is readable in every theme.** It was faint text trailing at the right + edge, which put the smallest, palest thing on screen in the exact corner where the themes that + imitate a screen are darkest — measured at well under half the contrast it needed, and effectively + invisible on the CRT and amber terminal themes. It now sits in a small pill with its own + background and border, so it reads as a deliberate element rather than trailing text and survives + a busy chat wallpaper too. The corner shading on those themes was also too heavy in general and + has been eased, which helps ordinary text near the edges of the pane as well. - **Secondary text is readable in every theme.** Timestamps, file paths, status lines and the hints under headings were below the accessibility contrast floor in half the shipped themes — several faithfully so, since palettes like Dracula and One Dark ship famously faint comment colours diff --git a/src/MandoCode.Desktop/Assets/web/transcript/transcript.css b/src/MandoCode.Desktop/Assets/web/transcript/transcript.css index b5176af..412a73f 100644 --- a/src/MandoCode.Desktop/Assets/web/transcript/transcript.css +++ b/src/MandoCode.Desktop/Assets/web/transcript/transcript.css @@ -89,7 +89,7 @@ linear-gradient(to bottom, transparent calc(66.6% - 1px), rgba(0,0,0,0.30) 66.6%, transparent calc(66.6% + 1px)), /* tube-edge vignette */ - radial-gradient(ellipse 100% 100% at center, transparent 60%, rgba(0,0,0,0.55) 100%); + radial-gradient(ellipse 100% 100% at center, transparent 60%, rgba(0,0,0,0.34) 100%); } /* ---- Media-matched background image (Appearance toggle) ------------------------------- Opt-in, and off by default: a chat background in full colour sits perfectly well behind any of @@ -279,7 +279,7 @@ content: ""; position: fixed; inset: 0; z-index: 9999; pointer-events: none; background: /* the smoked filter: darkest at the edges where the glass meets the bezel */ - radial-gradient(ellipse 120% 120% at center, rgba(0,0,0,0) 55%, rgba(0,0,0,0.45) 100%), + radial-gradient(ellipse 120% 120% at center, rgba(0,0,0,0) 55%, rgba(0,0,0,0.28) 100%), /* a faint vertical segment ruling — the grid the anodes are printed on */ repeating-linear-gradient(to right, rgba(0,0,0,0.10) 0 1px, rgba(0,0,0,0) 1px 4px); @@ -332,7 +332,7 @@ /* a scratch down the gate */ linear-gradient(to bottom, rgba(20,30,40,0.10) 0 100%), /* the lens vignette — heavier than a tube's, because it is an optical edge, not a curve */ - radial-gradient(ellipse 88% 92% at center, rgba(0,0,0,0) 42%, rgba(30,45,60,0.42) 100%); + radial-gradient(ellipse 88% 92% at center, rgba(0,0,0,0) 42%, rgba(30,45,60,0.26) 100%); background-size: 100% 100%, 100% 100%, 100% 100%, 100% 100%, 100% 100%, 1px 100%, 100% 100%; background-position: 0 0, 0 0, 0 0, 0 0, 0 0, 73% 0, 0 0; background-repeat: no-repeat; @@ -385,7 +385,7 @@ content: ""; position: fixed; inset: 0; z-index: 9999; pointer-events: none; /* Tube glass only: a gentle centre-bright falloff, no grille and no scanlines. */ background: radial-gradient(ellipse 110% 110% at center, - rgba(120, 255, 160, 0.022) 0%, rgba(0,0,0,0) 55%, rgba(0,0,0,0.38) 100%); + rgba(120, 255, 160, 0.022) 0%, rgba(0,0,0,0) 55%, rgba(0,0,0,0.24) 100%); } /* Dense code under a wide bloom becomes a smear — same trade the CRT block makes. */ html[data-vector] pre, html[data-vector] pre *, html[data-vector] code, @@ -563,7 +563,25 @@ .dim { color: var(--dim); } .sky { color: var(--sky); } .red { color: var(--red); } - .token-summary { text-align: right; font-size: 12px; } + /* The per-turn token metric. It used to be --dim text trailing at the right edge, and that put + the smallest, faintest thing in the app in the one place every themed overlay is darkest — its + corner vignette. Measured at 2.0-2.9:1 there, against a 4.5 floor. + + A palette-level contrast floor cannot fix that, which is worth understanding: scanlines + attenuate UNIFORMLY, so a raised floor answers them, but a vignette attenuates by position, so + the very same colour passes mid-pane and fails in the corner. There is no value to pick. + + So the metric gets a surface instead. On its own themed pill it carries its own background and + border, reads as a deliberate element rather than trailing text, and survives a busy chat + wallpaper — the same reasoning the plan-activity card already uses. De-emphasis now comes from + size and containment rather than from faintness, which is the durable way to do it: small and + bounded stays legible everywhere, whereas faint stops being legible the moment anything is + painted over it. */ + .token-summary { text-align: right; margin: 2px 0; } + .token-chip { font-size: 11.5px; padding: 2px 11px; color: var(--fg); + /* The status-pill family pads to a shared 190px so those line up with each other. This one is + not part of that set and should hug its own text. */ + min-width: 0; } /* Live plan status is operationally important, not ordinary transcript narration. Give it a high-opacity themed surface so a bright or busy chat wallpaper cannot wash out the active diff --git a/src/MandoCode.Desktop/Services/TranscriptHtmlBuilder.cs b/src/MandoCode.Desktop/Services/TranscriptHtmlBuilder.cs index b920cb2..57dee3f 100644 --- a/src/MandoCode.Desktop/Services/TranscriptHtmlBuilder.cs +++ b/src/MandoCode.Desktop/Services/TranscriptHtmlBuilder.cs @@ -345,8 +345,18 @@ private static string ActivityIconAttribute(string? icon) => _ => "▶️", }; + /// + /// The per-turn token metric, as a pill rather than a trailing line of faint text. + /// + /// Note the wrapper is NOT chip-row, even though the pill inside is an ordinary + /// chip: drops every chip-row from a restored + /// session, on the grounds that status pills describe a dead process's live state. A token + /// count is the opposite — it is what that turn actually cost, and it belongs to the + /// conversation's history. Reusing the row class here would have silently deleted these from + /// every reopened session. + /// public string TokenSummary(string text) => - $"
{E(text)}
"; + $"
{E(text)}
"; public string HelpCard(IEnumerable<(string Command, string Description)> rows) {