fix(menu): #131 — size text menus in ch with a cap, not in % of the monitor
All checks were successful
Check / eval (push) Successful in 3m49s

Bernardo saw Recovery ellipsize on a 2560x1440 external, which falsified
the item's model (40% + Inter 11 → "only below 1920"). Real cause: text
menus render through themes/<slug>/rofi.rasi, where boreal + neon-glass
pinned width: 620px (fixed — a wide panel buys nothing) while seven used
40%, and the font is whatever that file says — mostly monospace, far
wider than the Inter that was measured. The modelled combination ships in
no theme.

The insight: a menu must fit its longest label, which is a count of
characters in the THEME's font — so the window must scale with the font,
not the screen. 40% gives a 1366 panel 546px and a 2560 one 1024px for
the same row, and a 14pt mono theme needs ~25% more room than an 11pt one
on both; a percentage cannot see either fact.

Rofi has the right unit (`ch` = width of one digit in the current font)
and `calc( a min b )` to cap it, so every text menu — generated and all
nine whole-swaps — is now `width: calc( 84ch min 65% )`. 84ch fits the
longest row we ship (69 chars) plus icon and padding; the cap is
Bernardo's condition (never sprawl on a low-res panel) and it is
measured, not assumed.

Verified on hardware, not by arithmetic: 84ch = 756px in GeistMono 11 and
924px in JetBrainsMono 14; `calc( 84ch min 300px )` → 300px, so the clamp
really clamps. Screenshots at both ends — the real Recovery menu at 756px
(29% of 2560) with every label complete, and the Acer worst case
reproduced pixel-exactly (888px = 65% of 1366, JetBrainsMono 14) also
complete. Grid views override width per-invocation and are untouched.
checks.rofi-text-width guards the class, proven by pinning boreal back to
620px and watching it fail by name. Acer V3 queued for its own fontconfig.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-16 10:20:49 +01:00
parent 39c8b3e0eb
commit 5aad3153ce
15 changed files with 106 additions and 71 deletions

View File

@@ -1473,6 +1473,44 @@
in
assert ok; pkgs.runCommand "nomarchy-waybar-swap-env" { } "touch $out";
# A text menu must fit its longest label, which is a count of
# characters — so the picker has to be sized in the theme's own font
# (`ch`), never in % of the monitor or fixed px (#131: boreal pinned
# 620px and truncated Recovery on a 2560 panel; 40% themes truncate at
# 1366 while looking fine at 2560, and a 14pt mono theme needs ~25%
# more room than an 11pt one on both). The `min` cap is the other half
# — without it a big font on a small panel sprawls across the screen.
# Grid views (theme picker, drun) legitimately size by icons and
# override width per-invocation; this only governs the base theme.
rofi-text-width =
let
inherit (nixpkgs) lib;
expect = cond: msg: lib.assertMsg cond "rofi-text-width: ${msg}";
swaps = lib.filter
(n: builtins.pathExists (./themes + "/${n}/rofi.rasi"))
(builtins.attrNames (builtins.readDir ./themes));
widthOf = name:
let
text = builtins.readFile (./themes + "/${name}/rofi.rasi");
m = builtins.match ".*window[[:space:]]*\\{[^}]*width:[[:space:]]*([^;]+);.*" text;
in
if m == null then null else lib.head m;
bad = lib.filter
(name:
let w = widthOf name; in
w == null
|| builtins.match ".*ch.*" w == null
|| builtins.match ".*min.*" w == null)
swaps;
ok =
expect (swaps != [ ])
"fixture drift: no theme ships a rofi.rasi the guard is checking nothing"
&& expect (bad == [ ])
("a whole-swap sizes its text menu without `ch` and a `min` cap, so it "
+ "cannot fit its own font's labels: " + lib.concatStringsSep ", " bad);
in
assert ok; pkgs.runCommand "nomarchy-rofi-text-width" { } "touch $out";
# Docking artifact guard: default/no-profile Home Manager output
# must still launch both hotplug watchers, put Rofi on the focused
# monitor, expose the manual keyboard picker, and never ship the