aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/PLAN-per-host-overrides.org217
-rw-r--r--todo.org47
2 files changed, 260 insertions, 4 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?
diff --git a/todo.org b/todo.org
index 2810577..ad9cfd7 100644
--- a/todo.org
+++ b/todo.org
@@ -46,6 +46,42 @@ The =protonmail-bridge= package ships an enabled systemd user service (=/usr/lib
Fix applied per-machine 2026-05-22: =systemctl --user disable --now protonmail-bridge.service=, leaving the Hyprland exec-once GUI as the sole bridge (tray icon returns, served cert matches, =mbsync -a= clean). A fresh install re-enables the package service, so make it durable: mask/disable =protonmail-bridge.service= during install (likely in =scripts/cmail-setup-finish.sh=) and document that the Hyprland exec-once is the intended launcher — never run both. Source: handoff from .emacs.d 2026-05-22.
+** TODO [#C] Pocketbook development backlog :pocketbook:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-05-26
+:END:
+Pocketbook (GTK4 layer-shell notes panel, toggled via waybar) was pulled from publication 2026-05-26 — github repo + cjennings.net repo deleted, mirror hook removed — and folded into this repo at =pocketbook/= until it's ready to spin back out. Src-layout Python package with pytest tests and a Makefile. Develop it in-tree; the backing modules are =store/note/panel/layer_shell/app/note_widget= + =style.css=.
+
+Backlog (unordered; promote items to their own dated tasks as they're picked up):
+
+- Configurable options, possibly a dedicated configuration panel.
+- Lose-focus hides pocketbook — configurable on/off.
+- Configurable display order: chronological by creation date (asc/desc), manual, alphabetical (asc/desc).
+- Search / filter notes.
+- Global toggle keybind (Hyprland =bind=) alongside the waybar click; document the waybar integration.
+- Note CRUD polish (create/edit/delete) + optional markdown rendering.
+- Pin / favorite notes.
+- Tags or notebooks / categories.
+- Persistence: confirm store format + =~/.local/share/pocketbook/= location, add versioning/migration, decide a backup/sync story.
+- Theming: track the dupre/hudson theme system so =style.css= follows =set-theme=.
+- Layer-shell geometry config (anchor edge, width, margins) + HiDPI / multi-monitor behavior — ties into [[file:docs/PLAN-per-host-overrides.org][per-host overrides]] scaling work.
+- Config file format (toml) + reload-without-restart.
+- Expand test coverage (TDD per testing standards; =tests/= already exists).
+- Release prep for the eventual spin-back-out: pyproject metadata, version, license.
+- Re-wire the archsetup install (gtk4-layer-shell dep + install step + post-install clone) when pocketbook ships. Removed 2026-05-26 — see git history of =archsetup= / =scripts/post-install.sh=.
+
+** TODO [#B] Waybar timer module :waybar:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-05-26
+:END:
+A custom waybar module providing three time-keeping functions, surfaced in the bar with click/scroll controls and dunst notifications on completion.
+
+- *Alarm* — fire a notification at a wall-clock time (e.g. 2:00pm). Builds on the existing =notify= + =at= pattern from protocols.org.
+- *Timer* — count down a duration (e.g. 25m) and notify when it elapses.
+- *Pomodoro* — alternating work/break cycles (default 25/5, long break after 4) with the bar showing phase + remaining time.
+
+Implementation notes (to flesh out when picked up): waybar =custom= module(s) with =exec= polling or a persistent =exec= script emitting JSON; click actions to start/pause/reset; a small state file under =~/.local/state= or =~/.local/var=. Lives in the hyprland tier (=dotfiles/hyprland/.config/waybar/= + a backing script in =hyprland/.local/bin/=). TDD the backing script per testing.md.
+
** DOING [#A] Separate dotfiles from archsetup
SCHEDULED: <2026-05-21 Thu>
:PROPERTIES:
@@ -97,6 +133,11 @@ Now unblocked (ratio + velox both migrated). Remove =dotfiles/= from the archset
*** TODO [#B] Cleaner per-machine override mechanism for the dotfiles repo
velox keeps laptop-specific configs (foot font, pypr scratchpad sizing for 2256x1504, waybar battery) as local REAL files shadowing the stow symlinks. That's fragile: any =make restow= on velox re-conflicts (hit exactly this during the 2026-05-22 migration — stow aborts on the real files). The =~/.dotfiles= model needs a real per-machine override story (a =minimal/=-style per-host package, a documented local-override convention with =.stow-local-ignore=, or host-conditional includes) so overrides survive restows without manual backup/restore.
+Spec: [[file:docs/PLAN-per-host-overrides.org][docs/PLAN-per-host-overrides.org]] — gated on review before implementation.
+
+**** 2026-05-26 Tue @ 10:21:08 -0500 AI Response: Spec written, gated on review
+Surfaced by a HiDPI scaling failure: a per-app =QT_SCALE_FACTOR=1.5= in the shared =Zoom.desktop= (meant for velox) made Zoom open enormous on ratio. Reverted that patch to plain =/usr/bin/zoom %U=; the durable fix is this mechanism. Proposed approach: a per-host stow tier (=ratio/=, =velox/=) stowed as =common + hyprland + $(uname -n)=, with the existing =conf.d/*.conf= glob as the first clean tenant — move =local.conf= out of the shared =hyprland/= tier into per-host tiers so each machine gets its own (HiDPI monitor scale + =env = QT_SCALE_FACTOR/GDK_SCALE= on velox, minimal on ratio). XWayland apps don't scale via the compositor (=force_zero_scaling=true=), so toolkit env vars set in =conf.d= are the right layer — kills per-app =.desktop= hacks. Open question in the spec: whole-file configs with no include directive (waybar JSON, pypr toml) need a separate strategy. Full design + 5 open questions for Craig in the spec.
+
*** TODO [#A] Verify Phase 2 in the VM (hyprland + none) — pending clean run
Phase 2 shipped without a full VM integration pass: four runs hit env issues (pacman timeout, an SSH drop at 60 min, then a port-2222 collision with an active archangel VM). The fixed clone code was exercised by run 3 and run 4 ran 60 min past it, but the stow-per-DESKTOP_ENV paths — especially =none=/minimal, which no run reached — are unverified end-to-end. Close the gap: =make test= (hyprland) and a =DESKTOP_ENV=none= run, once port 2222 is free.
@@ -289,10 +330,8 @@ Paired Logi M650 via ~bluetoothctl scan on~, ~pair~, ~trust~.
This is inherently interactive (scan, select device, pair, trust).
Consider: document as post-install step. No automation possible.
-*** TODO Review: pocketbook not installed
-Custom Python package from ~/code/pocketbook needed ~pip install --user -e .~
-Data in ~/.local/share/pocketbook/ needed to be copied from ratio.
-Consider: document in post-install steps (not automatable in archsetup).
+*** 2026-05-26 Tue @ 13:32:31 -0500 pocketbook install concern moot — pulled from publication, folded in-tree
+Resolved by removing pocketbook from archsetup's provisioning entirely. It's nowhere near ready, so the github mirror + cjennings.net repo were deleted and the project was folded into the archsetup tree at =pocketbook/=. Dropped the =gtk4-layer-shell= dep + =pip_install= from =archsetup= and the clone from =scripts/post-install.sh=. No fresh install pulls pocketbook now, so "not installed on velox" no longer applies. Re-wiring the install is tracked in the new pocketbook development backlog.
*** TODO Review: Tailscale needs login after install
~tailscaled~ service was enabled but needed ~tailscale up~ for interactive auth.