Files
DotFiles/CLAUDE.md
T
haylan 2227a23630 Document the confd/hyprpaper config rewrite traps and pin known versions
Both bugs fixed in the preceding commits came from assuming an older
config syntax was still valid. Recorded the gotcha directly on the
relevant Architecture bullets, plus a version table for every
dotfiles-managed package so future edits check the installed version
against upstream's current schema instead of repeating the mistake.
2026-07-07 23:27:34 +02:00

7.4 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What this repo is

Master dotfiles repo for a Hyprland desktop on Arch Linux. It has two halves:

  • src/ — a small interactive TypeScript CLI (install/update/remove) that manages packages and dotfiles.
  • packages/ — the actual dotfiles, laid out as GNU Stow packages (one top-level dir per app, mirroring $HOME, e.g. packages/waybar/.config/waybar/config).

The CLI is the only orchestration layer; it shells out to the real stow, pacman, and yay binaries rather than reimplementing symlinking or package management.

Commands

npm install
npm start install   # interactive: choose packages, installs them, backs up conflicting files, symlinks into $HOME
npm start update     # pacman -Syu + yay -Syu, then re-links (stow --restow) any new files
npm start remove     # removes symlinks (stow -D), restores the most recent backup (does NOT uninstall packages)
npx tsc --noEmit     # typecheck
npm test             # runs test/ via Node's built-in test runner (node:test) through tsx
./docker/test.sh "npm test"   # run the full suite, including stow-dependent tests skipped on a bare host

There is no build step — tsx runs the TypeScript directly.

Testing

test/*.test.ts uses Node's built-in node:test + node:assert (via tsx --test) — no test framework dependency was added. test/gpu.test.ts and test/packages.test.ts are pure unit tests. test/stow.test.ts exercises the real stow binary and real filesystem calls against a throwaway $HOME (a mkdtempSync dir, swapped in via process.env.HOME before stow.ts is dynamically imported, since os.homedir() reads $HOME and the module's path constants are computed at import time); each case is skipped when stow isn't installed, which is the case on a bare host — run ./docker/test.sh "npm test" to execute those for real inside a container that has stow.

docker/test.sh (see docker/Dockerfile) spins up a disposable Arch container with stow/nodejs/npm/git/sudo preinstalled and the repo copied to a writable path inside it, so npm install, Stow symlinks, and backups never touch the host. Run it with no args for an interactive shell, or pass a command string to run non-interactively.

Architecture

  • src/packages.ts — the package model. Each entry has a tier (required installs with no prompt; recommended is offered via checkbox), an optional group for mutually-exclusive alternatives (e.g. waybar vs an alternative both having group: "bar", only one gets picked via a select prompt — bar and launcher currently each have just one member but are kept as groups so alternatives can be added the same way), and an optional dir for when a package's Stow directory name needs to differ from the package name. toDirs() converts chosen package names to their Stow directory names.
  • src/gpu.tsdetectGpuVendors() shells out to lspci and matches VGA/3D controller lines against NVIDIA/AMD/Intel keywords (word-boundary regexes — a naive /amd|ati/i used to false-positive on the "ati" inside "Corporation"); driverPackagesFor() maps detected vendors to their Arch driver packages. install.ts shows every detected vendor's packages in a pre-checked confirm prompt (installing all of them on hybrid/Optimus laptops), and if NVIDIA is kept, stow.ts's writeNvidiaEnvConfig() writes ~/.config/hypr/confd/nvidia.lua (machine-generated, not stowed) with the Wayland env vars Hyprland's NVIDIA docs recommend.
  • src/stow.ts — all filesystem/process side effects live here: bootstrapping yay and oh-my-zsh/zinit if missing, installing packages via yay, enabling the sddm systemd service (ensureSddmEnabled), backing up real (non-symlink) files that would collide with a Stow target into ~/.dotfiles-backup/<timestamp>/, and wrapping stow/--restow/-D (always invoked with an explicit -d <PACKAGES_DIR> so behavior doesn't depend on cwd). Also reads/writes ~/.config/dotfiles/state.json, which records which package dirs were chosen at install time — update and remove act on this list rather than re-prompting.
  • src/install.ts, src/update.ts, src/remove.ts — the three subcommands, each a thin sequence of calls into stow.ts, using @clack/prompts for interactive selection in install.ts.
  • src/cli.ts — dispatches install|update|remove via Node's built-in node:util.parseArgs.
  • packages/hyprland/.config/hypr/hyprland.lua — Hyprland's config, in its current Lua format (hl.config/hl.bind/hl.monitor/hl.on/hl.env), not the old hyprland.conf ini/hyprlang syntax deprecated since Hyprland 0.55. Easy detail to get wrong when editing — check wiki.hypr.land (or the raw hyprland-wiki GitHub markdown) for current syntax rather than assuming the old ini style. require() inside it resolves dots in the module string as directory separators (standard Lua package.path behavior) — that's why the machine-generated config dir is named confd, not conf.d: a literal dot in the directory name would make require("conf.d.foo") look for conf/d/foo.lua instead and silently fail (wrapped in pcall, so it fails with no visible error).
  • packages/hyprpaper/.config/hypr/hyprpaper.conf — hyprpaper's config, in its current hyprlang block format (wallpaper { monitor = ... path = ... fit_mode = ... }), not the old preload = / wallpaper = monitor,path key=value syntax removed in hyprpaper 0.8.0. preload doesn't exist anymore at all. Verify against src/config/ConfigManager.cpp in the hyprwm/hyprpaper repo (m_config.addSpecialConfigValue(...) calls list every valid key) rather than assuming the old ini style — same trap as hyprland.lua above.

Pinned reference versions (Arch, checked 2026-07-07)

These packages have had breaking, from-scratch config rewrites in living memory — don't assume a config snippet found online (or from an older revision of this repo) is still valid without checking the installed version against the package's current source/changelog:

package version note
hyprland 0.55.4 Lua config (hyprland.lua) since 0.55; older configs use hyprland.conf hyprlang/ini syntax.
hyprpaper 0.8.4 Rewritten onto hyprtoolkit in 0.8.0 ("configs are broken and much simplified" — upstream's own words); new block-based wallpaper { } syntax, preload removed.

Other managed packages (waybar 0.15.0, mako 1.11.0, hyprlauncher 0.1.6, kitty 0.47.4, foot 1.27.0, zsh 5.9.1, xdg-desktop-portal-hyprland 1.3.12, sddm 0.21.0, stow 2.4.1) are recorded here for reference, but haven't been confirmed to carry a similar breaking-rewrite risk — re-check versions with pacman -Qi <pkg> before trusting this table, since this is a rolling-release Arch machine and it will drift.

Adding a new app

Add a directory under packages/ mirroring its $HOME layout (e.g. packages/foo/.config/foo/config), then add a matching entry to src/packages.ts (dir if the Stow directory name differs from the package name, group if it's a mutually-exclusive alternative to another package).

Backup/restore

Backups are stored under ~/.dotfiles-backup/<timestamp>/, mirroring the relative path of whatever real file was moved aside before stowing. remove restores only the most recent backup automatically; restoring an older one is a manual cp -a ~/.dotfiles-backup/<timestamp>/. ~/.