aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
blob: fd40b57a387d11331474ef1ee69dab01702225c6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# AGENTS.md

Guidance for agents working in this repository.

## What this is

One visual identity for a Hyprland desktop: Catppuccin Macchiato, Noto Sans for
UI text, Inconsolata Nerd Font Mono for monospace. It covers rofi, dunst,
Hyprland window borders, conky, Qt5/Qt6, GTK3/GTK4, waybar, kitty and neovim.

The repo holds the canonical copies. `./install.sh` symlinks them into place, so
the working config cannot drift from what is committed. Everything outside
`~/.config/rofi/udt/` and `~/bin/` was edited in place; `docs/MIGRATION.md` is
the record of what changed and where the backups are.

## One palette, many consumers

Colours are written down once. `palette/<scheme>.conf` holds them under the
scheme's own names, `palette/roles-<scheme>.conf` says what each is for, and
`palette/roles.conf` is a single `scheme = ` line selecting the pair.
`bin/udt-palette` renders that into every consumer's own syntax; `install.sh`
runs it first, before anything is linked.

Four schemes ship: `macchiato`, `tokyo-night`, `nord`, `dracula`. Switching is
one line plus `./install.sh`.

Generated files are gitignored, because tracking them would turn every scheme
switch into a diff. Editing one is pointless: the next install overwrites it.
The generated set is `rofi/udt/palette.rasi`, `templates/dunstrc`,
`templates/conky.conf`, `templates/waybar/theme.css`,
`templates/terminal/kitty-theme.conf` and `bin/udt_colors.py`. The last is
imported by `udt-accent`, which is why its accent table follows the scheme.

Roles are a flat namespace, so a name collides across sections: conky's outline
is `body_outline` because waybar already has `outline`. A role value is a
palette name with two optional modifiers, `name/75` for 75% alpha and
`name*50` for half brightness. The second exists only because GTK's
`shade(@main-bg, 0.5)` has no palette name to point at: half-brightness crust
is darker than the darkest colour any of these schemes ships.

**Adding a scheme means adding two files, never editing a consumer.** Every
scheme must define the same role set; `udt-palette --selftest` fails if one
drifts, which is what stops a switch from breaking a config nobody looked at.

## The accent

One colour moves: `@accent`. `bin/udt-accent` reads the current wallpaper,
extracts its signature colour, and snaps it to the nearest of ten Catppuccin
accents by perceptual hue in CIELAB. `wallp` calls it on every wallpaper change.

It writes five things: `~/.cache/wal/udt-accent.rasi` (rofi),
`~/.cache/wal/udt-border.lua` (Hyprland), the accent substitution in
`~/.cache/wal/dunstrc`, `~/.cache/wal/colors.json` (Firefox, via
pywalfox), and `~/.cache/wal/udt-palette.qml` (quickshell).

The QML palette is the one output that is not only the accent: it carries
the whole structural palette too, parsed out of `rofi/udt/palette.rasi`
rather than duplicated in the script. That file stays the single place the
Macchiato values are written down. Quickshell components watch the generated
file and re-read it in place, so a palette edit reaches a running shell
without restarting it; see the `quickshell` repo alongside this one.

**Extraction is deliberately isolated from the pywal cache.** It calls
`pywal.backends.colorz.get()` directly, which returns a list and writes nothing.
Running `wal -i` instead would rewrite all of `~/.cache/wal`, including the
terminal's ANSI colours, and that is the exact failure this design exists to
avoid. If you change `signature_color()`, verify it still writes nothing.

`colors.json` is the one pywal file `udt-accent` does rewrite, and it rewrites
it wholesale rather than editing it: a fixed Macchiato palette with the accent
in the cursor and the two highlight slots. That is deliberate. pywalfox reads
`colors.json` and nothing else, so it is the only way Firefox can follow the
accent, and overwriting it costs nothing because nothing else reads it. In
particular kitty reads `~/.config/kitty/current-theme.conf`, not this file, so
the terminal palette stays fixed no matter what the wallpaper looks like. The
write lands after `wallp` has already run `wal --theme`, so it wins; reorder
those two and pywal's wallpaper-derived colours come back.

Not everything tracks the wallpaper. rofi, dunst, Hyprland borders and Firefox
do; conky, Qt, GTK and waybar sit on fixed lavender, which is also the fallback
when a wallpaper is too grey to snap. Qt and GTK apps only reread a theme on restart,
so a moving accent there would leave running apps disagreeing with new ones.

## Verifying visual changes

`rofi -no-config -theme <absolute-path> -dump-theme >/dev/null` catches syntax
errors without a display. It does **not** tell you what renders: it normalises
values, prints `(null)` for every image, and collapses multi-value margins.

For anything visual, screenshot and measure:

    grim -o DP-1 /tmp/shot.png

Reasoning about rofi's box model was wrong repeatedly during this project;
measuring pixels was right every time. When a widget looks wrong, measure its
bounding box before theorising about why.

Rofi needs a display, so these steps run in the user's session. Do not judge
them by exit code: rofi exits non-zero for ordinary reasons such as Escape.

## Things that are not where they look

- **rofi is 2.0.0**, not the 1.7.3 its config header claims. `rofi -v` is the
  authority. `-width` is gone; width lives in the theme.
- **`userimage` in `powermenu.rasi` only honours its margin.** A `width` with
  `expand: false` collapses it to a line, and a percentage margin resolves
  against the whole window rather than the padded column. Its 230px margin is
  tied to the 680px column in `mainbox`; both move together.
- **A layout that hardcodes `children` silently drops `-mesg`.** That is why
  `list.rasi` lists the message widget even though most callers pass nothing.
- **`ClearlyU` is installed and claims large parts of the Private Use Area**, so
  it can win fontconfig's fallback and draw a blank where another font has the
  glyph. `fc-match ":charset=XXXX" family` says who actually wins. The `U+F0xxx`
  Nerd Font ranges avoid the problem.
- **GTK3's theme comes from gsettings on Wayland**, not `settings.ini`. It was
  pinned to `Breeze`, silently overriding the file, for who knows how long.
- **GTK4 ignores `gtk-theme-name`.** Its stylesheet is symlinked into
  `~/.config/gtk-4.0/theme` and imported by `gtk.css`.
- **Hyprland's Lua parser refuses `hyprctl keyword`** ("keyword can't work with
  non-legacy parsers") and has no source directive. The border colours are a Lua
  table read with `dofile()`.
- **`wallp` starts `swaybg` in the background**, which inherits the shell's
  stdout, so a pipe or command substitution appears to hang after `wallp` has
  already finished. Redirect its output when calling it from a script.
- **waybar's own CLAUDE.md documents a stale CSS load order.** The
  `modules-*.css` and `states.css` files it names are not imported by anything;
  `style.css` pulls in `styles/main.css`. One of the dead files holds a
  `font-family` that looks authoritative but does nothing.

## Editing rules

- **conky**: change colours in the `conky.config` block only. `conky.text` is
  laid out with absolute `${goto}` pixel offsets tuned to label widths, so
  editing text means re-tuning every goto on that line. See
  `~/.config/conky/CLAUDE.md`. Conky does not hot-reload.
- **Committed files carry no home paths.** A gitleaks hook blocks them, and it
  has been right every time. Where a live config mixes theme keys with personal
  data (`qt6ct.conf` has an `ignored_applications` list), track only the
  theme-relevant keys.
- **Themes that reference an image cannot be committed with a real path.**
  `launcher.rasi` carries an `@WPAPER@` placeholder that `install.sh` rewrites.
  rofi does not expand `~` inside a `url()`.
- After editing anything under `rofi/udt/` or `palette/`, run `./install.sh`.
- `bin/udt-palette --selftest` resolves every role in every scheme. Run it
  after touching a palette, a role map, or the generator.
- `bin/udt-accent --selftest` covers the snapping logic. Run it after touching
  the colour maths.

## Reloading

| | How |
| --- | --- |
| rofi | nothing; the theme is read per launch |
| dunst | `udt-accent` restarts it |
| Firefox | `udt-accent` runs `pywalfox update` |
| Hyprland | `hyprctl reload` |
| waybar | `killall -SIGUSR2 waybar` |
| kitty | `pkill -USR1 -x kitty` |
| conky | restart it; no hot-reload |
| Qt / GTK apps | restart the app |