They were written down five times: ThemeProfileModel.js for QML,
looks.lua for the compositor, and again in panama-theme-apps and
panama-lock. The GNOME accent-name mapping was a sixth list. Adding a
ninth accent meant editing all of them, and the file most likely to be
missed was the lock screen, which fails silently -- the machine locks
in last season's colour and nothing says why. panama-theme-apps
admitted it in a comment: "there is no shared source between QML and a
shell script".
config/palette.json is that source now. looks.lua reads it through a
new prefs.readJson, which uses the same never-raise parser the settings
store uses, so an unreadable palette costs the accent colours and never
the compositor config. The two shell generators read it through
scripts/panama-palette, which also carries the hex-to-rgb conversion
hyprlock needs and the GNOME member lookup.
QML keeps its table, because a .js module imported into QML cannot read
a file. That is still a copy, so the palette contract compares the two
value by value -- every accent, every field -- and fails on any
disagreement. Verified by planting a wrong hex and watching it name the
exact field.
The adwaita contract used to check the shell's own copy of the GNOME
mapping. It now checks that the shell resolves through the palette, and
fails if that copy ever grows back.
Files, Papers, Loupe and every other libadwaita application read their
accent from the Settings portal, so they rendered in GNOME blue no
matter which accent this desktop was set to -- correct on our own
surfaces, wrong on half the screen.
xdg-desktop-portal-gtk cannot serve org.freedesktop.appearance
accent-color at all; the string does not appear in the 1.15.3 binary.
The gnome backend serves it, so Settings now routes to gnome with gtk
still listed behind it -- the frontend merges Settings backends in
order, so color-scheme keeps resolving if the gnome backend is ever
unavailable.
GNOME's accent-color is a fixed enum of nine names rather than a color,
so each of our eight accents carries its nearest member. Nearest by hue
rather than by name: rose maps to red, because it is the red role in
this palette.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
colour -> color, behaviour -> behavior, centre -> center, favourite ->
favorite, and about twenty other pairs, applied consistently across
comments, docs, error/UI copy, and a handful of QML identifiers that
used the British spelling as their actual name: SystemSettings'
serialiseValue/serialiseTable/normaliseGradient, Displays'
normaliseModes, Wallpaper's normalisePolicy, SettingsBackup's
serialiseHomeState, DateTime's ntpSynchronised property, Clipboard's
_normalise helper, and ShortcutCapture's cancelled signal (with its
onCancelled handler in ShortcutsPage.qml). Every call site and the two
tests that assert on the literal source text (settings-ownership and
settings-backup-live contracts) were updated in lockstep.
Left untouched: config/dot/espanso/match/packages/misspell-en/ is a
vendored third-party autocorrect dictionary -- its entries are typo
corrections, not our prose, and rewriting them would fight the
package's own purpose (and any future re-sync from upstream).
The already-American `favorites` property (Home page pinned
accessories) was never actually misspelled -- only nearby comments and
error strings said "favourites" -- so no data migration was needed
there.
Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
The recent accent-colour setting only reached part of the desktop.
looks.lua still hardcoded the focused-window border and glow to blue,
so any hyprctl reload -- or every fresh session, for about a second --
reverted a chosen accent; it now reads accentName the same way it
already read colorScheme. The lock screen and terminal stayed blue
regardless of the chosen accent despite the setting's own description
claiming otherwise; panama-lock and panama-theme-apps now resolve and
apply the real accent.
AccentPicker built its swatch model from Theme.accents directly
instead of the schema's own options list, so the two could drift
silently; switched it to read the schema. Its hit target only covered
the swatch, not the name label added specifically for colour-vision
accessibility -- extended to the whole row. Settings search had no
route for the "appearance" group, so searching for the accent or
colour scheme landed on Home.
ColorScheme's hex-to-Hyprland helper assumed 6-digit colours and would
silently corrupt a future translucent one; fixed it to read from the
end of the string instead of the start. An accent-only change no
longer reruns the full colour-scheme pipeline. The gradient it builds
for the focused border now goes through SystemSettings' existing
serialiser instead of a second, under-escaped copy of the same logic.
The settings-ownership contract test enforced the old rule that
ColorScheme must never touch the focused border; updated it to verify
the real, intended rule instead of contradicting the code.
Claude-Session: https://claude.ai/code/session_01E6TJUAh41HaP25MVHWkhRZ
hyprlock.conf shipped with Tokyo Night Moon hardcoded in six places, so
choosing light mode left the lock screen dark. Every other surface had
been taught to follow the scheme this week -- kitty, GTK, the launcher,
btop, tmux, neovim -- and this was the one left, which is unfortunate,
because it is the screen a user sees most often and the worst possible
place to find a theming bug: you discover it while locked out of the
machine and cannot fix it from there.
It is now generated from a template on every scheme change, the same
shape kitty, GTK, tmux and btop already use, and seeded by link-dotfiles
so the first lock of a fresh install is themed rather than falling back
to hyprlock's bare grey default. hyprlock is launched fresh on each lock
(`pidof hyprlock || hyprlock`), so it picks the file up with no restart.
The dark output is byte-identical to the file it replaces, ignoring
comments -- verified by diff -- so nothing changes for anyone already in
dark mode.
One detail worth recording: hyprlock takes rgba(r, g, b, a) in DECIMAL,
not hex, so the template carries "R, G, B" triples where every other
theme file in this repository uses hex. Two values are the exception,
sitting inside Pango markup where hyprlock wants ##rrggbb. Getting
either wrong is not a parse error -- hyprlock ignores the value and uses
its own default, silently.
Which is why this has a contract. It generates both schemes into a
fixture, never the live config, and checks that no placeholder survives
substitution, that every colour is a well-formed decimal triple, that
the Pango values are well-formed hex, that a light lock screen is
actually light, and that the two schemes differ at all. Verified it
catches a hardcoded colour left in the template and a light mode built
from the dark palette, which is the original bug exactly.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
Two more applications that keep their own palette and so never followed
the desktop. btop was on "Default" and had never been themed at all;
tmux had Tokyo Night Moon hardcoded across seventeen lines, which meant
a dark status bar sitting under a light terminal in light mode.
Both themes are authored rather than borrowed. btop ships a
"tokyo-night" theme, but it is the Night variant (#1a1b26) where the
rest of this desktop is Moon (#222436), and two Tokyo Nights side by
side read as a mistake; it ships no Tokyo Night light theme at all.
Colours come from kitty's theme files so a terminal and what runs inside
it cannot disagree.
tmux follows kitty's shape: the colours move to themes/, tmux.conf
sources a generated current-theme.conf, and running servers are
re-sourced so an open session changes immediately rather than at next
launch. btop is different -- it OWNS btop.conf and rewrites it on exit,
so only the color_theme line is edited in place and the file is not
symlinked into the repository. btop reads its theme once at startup, so
a running instance keeps the old colours; forcing a restart would kill a
process the user is watching.
The tmux light theme took two passes. Mapping the palette role-for-role
put the standard Day accents on a mid-grey panel at 2.74:1 and 2.94:1 --
under the 3:1 floor, on a bar you read at a glance. It now uses Day's
darker accent variants on a lighter panel: 4.42:1 and 4.17:1, and
5.01:1 / 4.73:1 for the light text inside the inverted blocks.
The helper also now reports every target rather than only kitty. Saying
"kitty: applied" while silently skipping three other applications is
how a half-applied theme goes unnoticed.
Not changed: bat is configured with --theme ansi, which follows the
terminal's own palette, so it already tracks kitty with nothing to do.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
Reported symptom: Electron applications and a Chromium-based browser,
all set to follow the system, went light when the desktop went light and
never came back. Nothing reported an error, and the portal was serving
the correct value the whole time.
Cause: ColorScheme set gtk-theme to "Adwaita-dark" for dark and
"Adwaita" for light. Neither is installed on Fedora 44 -- only adw-gtk3
and adw-gtk3-dark are. GTK responds to an unknown theme name by falling
back to its built-in default, which is LIGHT. So asking for light worked
by accident, asking for dark silently produced light, and anything that
takes its cue from the GTK theme rather than the portal stayed light no
matter what org.freedesktop.appearance said. Verified: the portal emits
correctly in both directions, so this was never the portal's fault.
Second cause, the mirror of the first: gtk-3.0/settings.ini and
gtk-4.0/settings.ini were pinned to adw-gtk3-dark and prefer-dark=1 and
never regenerated. Under GNOME that file is ignored because
gnome-settings-daemon publishes over XSETTINGS; under Hyprland nothing
does, so for GTK3 it is authoritative -- and it contradicted the scheme
in light mode. They are now generated from a template on every switch,
gitignored as machine state, and seeded by link-dotfiles, the same shape
kitty's current-theme.conf already uses. These directories are symlinked
into the repository, so writing the live file directly would dirty the
working tree on every theme switch.
The failure was silent by construction, so it gets a test rather than a
comment: gtk-theme-contract asserts every theme name Panama sets is
actually installed, and that the generated GTK config agrees with the
scheme in both directions. Verified it catches both original bugs.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L
The scheme switch already reached everything that reads
org.freedesktop.appearance -- GTK4, Qt6, Chromium, Electron -- because
ColorScheme.qml writes the gsettings key those all watch. Applications
carrying their own palettes did not follow, so choosing light mode left
the two windows actually used all day, kitty and neovim, still dark.
kitty: the 32 colours move out of kitty.conf into themes/, and kitty.conf
ends with `include current-theme.conf`. The generated file is machine
state rather than configuration, so it is gitignored and link-dotfiles
seeds it on install -- otherwise a fresh checkout starts by complaining
about a missing include. Running terminals are re-coloured in place over
their control sockets; a restart is not needed.
neovim: reads settings.json directly, since it neither watches the portal
nor keeps a socket open. Tokyo Night ships Day in the same family as
Moon, so light mode keeps the editor's identity instead of turning it
into a different-looking application. The existing readability overrides
were written against Moon and are now dark-only -- applied to Day they
would have put light grey on a light background, the same problem they
exist to fix, inverted. Light mode gets one override of its own:
tokyonight's shipped comment colour measures 2.54:1 against Day's
background, under the 3:1 floor for secondary text, so it is replaced
with 3.25:1 -- readable, still dimmer than Normal's 4.52:1.
An editor already open when the scheme flips re-applies on FocusGained,
which is cheap and fires exactly when the mismatch would be noticed.
Verified both directions: kitty re-coloured 4 live terminals, and neovim
starts as tokyonight-day with background=light and tokyonight-moon with
background=dark.
Claude-Session: https://claude.ai/code/session_01BRvzt4H8XXLPVH5MyYdk9L