diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/PLAN-per-host-overrides.org | 217 |
1 files changed, 217 insertions, 0 deletions
diff --git a/docs/PLAN-per-host-overrides.org b/docs/PLAN-per-host-overrides.org new file mode 100644 index 0000000..fdeb3d1 --- /dev/null +++ b/docs/PLAN-per-host-overrides.org @@ -0,0 +1,217 @@ +#+TITLE: Per-Host Override Mechanism for the Dotfiles Repo +#+AUTHOR: Craig Jennings & Claude +#+DATE: 2026-05-26 + +* Status + +| Field | Value | +|--------------+-------------------------------------------------------------| +| State | Draft — gated on review before implementation | +| Trigger | Zoom launched enormous on ratio after a per-app QT_SCALE_FACTOR=1.5 patch meant for velox | +| Supersedes | The fragile "local real files shadowing stow" pattern on velox | +| Related task | =todo.org= → "Cleaner per-machine override mechanism for the dotfiles repo" | +| Related spec | [[file:PLAN-dotfiles-separation.org][PLAN-dotfiles-separation.org]] (the repo this extends) | + +* Problem + +The =~/.dotfiles= repo stows the same =common/= + =hyprland/= trees to every +machine. Both hosts (ratio = desktop, velox = Framework 13 laptop) symlink +identical files. There is no mechanism for a setting to differ per machine, and +the two machines genuinely differ in one structural way: *velox is HiDPI +(2256×1504), ratio is not.* + +Two concrete failures this causes: + +1. *Per-app scaling patches leak across hosts.* Someone set + =Exec=env QT_SCALE_FACTOR=1.5 /usr/bin/zoom %U= in + =hyprland/.local/share/applications/Zoom.desktop= to enlarge Zoom on velox's + HiDPI panel. Because that desktop file is shared via stow, the 1.5× also + applied on ratio, where Zoom then opened enormous. (Reverted to plain + =/usr/bin/zoom %U= on 2026-05-26; this spec is the durable fix for the + underlying need.) + +2. *velox's laptop overrides are fragile real files.* velox keeps + laptop-specific configs — =foot.ini= font size, =pypr= scratchpad sizing for + 2256×1504, waybar battery module — as *local real files shadowing the stow + symlinks*. Any =make restow= on velox re-conflicts: stow aborts on the real + files (hit exactly this during the 2026-05-22 migration). The override + survives only via manual backup/restore around every restow. + +* Background — how scaling works today + +Two lines in =hyprland/.config/hypr/hyprland.conf= set the scaling regime: + +- =monitor=,preferred,auto,auto= — Hyprland auto-scales each monitor by its DPI. + *Wayland-native apps already adapt per-machine automatically* — velox gets + fractional scaling, ratio gets 1×, with no config difference. Native apps are + not the problem. +- =xwayland { force_zero_scaling = true }= — XWayland apps (Zoom, any X11 app) + are deliberately *not* scaled by the compositor, so they stay sharp instead of + blurry-upscaled. The cost: on a HiDPI display they render tiny unless the app + scales itself via toolkit env vars (=GDK_SCALE=, =QT_SCALE_FACTOR=). + +So XWayland scaling has to come from environment variables, and *those env vars +need to differ per host.* The per-app =QT_SCALE_FACTOR= hack was the wrong layer +(one app, shared across hosts). The right layer is one per-host setting that +covers every XWayland app at once. + +* The seam already exists + +=hyprland.conf= line 342 already sources a glob: + +#+begin_src conf +source = $HOME/.config/hypr/conf.d/*.conf +#+end_src + +and the repo already ships =hyprland/.config/hypr/conf.d/local.conf= as a +"machine-local overrides" template (monitor scaling, keybinds, etc.). Two +properties make this the ideal mechanism: + +- *Glob source tolerates absence.* A host with no matching file just sources + nothing extra — no Hyprland error banner. +- *Last-wins.* Values set in =conf.d/local.conf= override the same keys set + earlier in =hyprland.conf=. + +The only flaw: =local.conf= currently lives in the *shared* =hyprland/= tier, so +it is one symlink shared by both machines. Editing it (through the symlink) +rewrites the single repo file and applies everywhere — and re-conflicts on +restow. *The fix is to make =local.conf= a per-host file instead of a shared +one.* + +* Goal + +A per-machine override that: + +1. Survives =make restow= without manual backup/restore. +2. Is version-controlled in the dotfiles repo (a fresh install of that host + gets its overrides automatically). +3. Covers the immediate HiDPI/XWayland-scaling need on velox. +4. Generalizes to the other per-host divergences (foot font, pypr sizing, + waybar battery) without per-app hacks. + +* Proposed mechanism — a per-host stow tier + +Add host-named stow packages alongside the existing =common/ dwm/ hyprland/ +minimal/= tiers: + +#+begin_example +~/.dotfiles/ + common/ + dwm/ + hyprland/ + minimal/ + ratio/ ← new: ratio-only files + velox/ ← new: velox-only files +#+end_example + +=make stow hyprland= stows =common + hyprland + <hostname>=. The host tier holds +*only files that exist nowhere else in the stow set*, so stow never conflicts. + +** Why no conflict + +GNU Stow refuses when two packages provide the same target path. So the host +tier cannot re-own a file already in =common/= or =hyprland/=. That constraint +is exactly why =conf.d/local.conf= is the right first tenant: once it is +*removed* from =hyprland/=, it exists *only* in the host tiers, and each host +symlinks its own copy with zero conflict. The Hyprland glob does the merge at +runtime. + +** First tenant: Hyprland per-host config (solves the scaling trigger) + +- Remove =local.conf= from =hyprland/.config/hypr/conf.d/=. +- Add =ratio/.config/hypr/conf.d/local.conf= and + =velox/.config/hypr/conf.d/local.conf=. + +=velox/...local.conf=: + +#+begin_src conf +# velox — Framework 13, HiDPI 2256×1504 +monitor = eDP-1,preferred,auto,1.566667 + +# XWayland apps don't scale via the compositor (force_zero_scaling=true), +# so scale the toolkits explicitly. Inherited by every app launched in the +# session, including XWayland clients spawned by the browser (e.g. Zoom). +env = GDK_SCALE,1.5 +env = QT_SCALE_FACTOR,1.5 +env = XCURSOR_SIZE,36 +#+end_src + +=ratio/...local.conf=: + +#+begin_src conf +# ratio — desktop, 1× scaling. Defaults in hyprland.conf are correct; +# nothing to override yet. File present so the host tier always has content. +#+end_src + +This removes the need for any per-app =QT_SCALE_FACTOR= in =.desktop= files. + +** Open design question: whole-file app configs + +=conf.d/local.conf= works cleanly because Hyprland merges fragments at runtime. +The other per-host divergences are *monolithic files* already present in a +shared tier, which a host tier cannot re-own via stow: + +| File | Native include? | Approach | +|------------------------------+----------------------------+----------| +| =foot.ini= | Yes — =include=path= | Keep base in shared tier; add =include=~/.config/foot/host.ini=; ship =host.ini= per host tier | +| waybar =config= (JSON) | No include directive | Either move the whole file into host tiers, or keep a documented local-override + =.stow-local-ignore= | +| pypr =config.toml= | No include directive | Same choice as waybar | + +Three candidate strategies for the no-include files, to decide in review: + +1. *Move the whole file into host tiers.* Cleanest at runtime, but duplicates + the ~95% shared content across =ratio/= and =velox/= — drift risk. +2. *Documented local-override + =.stow-local-ignore=.* Keep the shared file, + let the host keep a real file, and add the path to =.stow-local-ignore= so + restow skips it instead of aborting. Removes the fragility (no more abort) + but the override stays out of version control. +3. *Generator step.* A =make host-config= target (or an archsetup install step) + that assembles the per-host file from a shared base + a host fragment. Most + flexible, most machinery. + +Recommendation to start: strategy 1 for files with no include directive (small +count, low churn), native =include== for foot. Revisit if duplication drift +becomes real. + +* Implementation outline + +Gated on review. Rough phases: + +1. *Makefile — host tier.* Detect the host (=HOST := $(shell uname -n)= — note + the =hostname= binary is absent on these boxes; =uname -n= and + =cat /etc/hostname= both work). Add =$(HOST)= to the package list in =stow=, + =restow=, =reset=, =unstow=, guarded so a host with no package is skipped + with a clear message rather than a stow error. Update =help= text and the + archsetup =CLAUDE.md= Makefile section. + +2. *Hyprland per-host config.* Remove =local.conf= from =hyprland/=; create + =ratio/= and =velox/= host tiers each with =.config/hypr/conf.d/local.conf=. + Populate velox's with the HiDPI monitor scale + toolkit env; ratio's minimal. + +3. *Verify XWayland env propagation.* Confirm =env == lines in =conf.d= + actually reach XWayland clients launched outside the compositor (e.g. Zoom + spawned by the browser). Hyprland sets =env= for its children; the browser is + a session child, so its children should inherit. Verify on velox: launch Zoom + from a link, confirm normal size with no per-app patch. + +4. *Migrate velox's fragile overrides.* Move foot font / pypr sizing / waybar + battery off the local-real-file pattern into the chosen strategy from the + open question above. + +5. *archsetup install integration (optional follow-on).* Teach the installer to + stow the host tier for =DESKTOP_ENV in {dwm,hyprland}=. Decide whether the + host name comes from =/etc/hostname= at install time or a new conf key. + +* Open questions for Craig + +1. Host detection: =uname -n= / =/etc/hostname= auto-detect (proposed), or an + explicit =HOST== Makefile override / conf key? +2. Whole-file overrides (waybar, pypr): strategy 1 (whole file in host tier), + strategy 2 (=.stow-local-ignore= + local real file), or strategy 3 + (generator)? See the table above. +3. Scope now: just the Hyprland scaling fix (phases 1–3), or migrate velox's + existing overrides in the same pass (phase 4)? +4. Should =make stow= *always* include the host tier silently, or require it be + named (=make stow hyprland velox=) to keep the behavior explicit? +5. Does the archsetup installer need to know about host tiers (phase 5), or is + this a post-install =make= concern only? |
