Stop the deploy tier from reverting live theme state #43
+17
-6
@@ -80,10 +80,21 @@ wt/
|
||||
agent/memories/--home-blank-.omp-wt-*
|
||||
|
||||
# --- theme media: content the pipeline consumes, not configuration ---------
|
||||
# 23 wallpapers, 80 MiB. Tracked until 2026-10-20; every clone of a config repo
|
||||
# was carrying a library no diff can review. The files stay on disk because
|
||||
# ~/.local/share/noctalia/wallpapers symlinks into here, and every filename
|
||||
# carries its wallhaven ID, so a fresh box restores them with
|
||||
# `curl -L -o <name> https://wallhaven.cc/w/<id>` + deploy.sh.
|
||||
# Law: omp-docs/tools/dotfiles-deploy.md
|
||||
# 23 wallpapers, 80 MiB. Tracked until 2026-10-20: every clone of a config repo was
|
||||
# carrying a library no diff can review. The live library at
|
||||
# ~/.local/share/noctalia/wallpapers is a real directory of real files (measured
|
||||
# 2026-10-20), so this path in the repo was a duplicate of it, not its source — and
|
||||
# note that untracking deletes the copy in every other checkout regardless of
|
||||
# `git rm --cached` on the branch. A fresh box starts with an empty library; every
|
||||
# filename carries its wallhaven ID, so `curl -L -o <name> https://wallhaven.cc/w/<id>`
|
||||
# puts it back. Law: omp-docs/tools/dotfiles-deploy.md
|
||||
dotfiles/noctalia/.local/share/noctalia/wallpapers/
|
||||
|
||||
# --- generated theme output: machine-written, never reviewable, never deployed ---
|
||||
# noctalia/matugen rewrite these in place; a tracked copy is a stale snapshot that
|
||||
# deploy.sh would push back over a live palette. Patterns match the exclusion list in
|
||||
# scripts/deploy.sh. Law: omp-docs/inventory/home-config-census.md, Refs #42
|
||||
dotfiles/**/themes/noctalia*
|
||||
dotfiles/**/colors/noctalia*
|
||||
dotfiles/**/noctalia.*
|
||||
dotfiles/**/noctalia-*
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
layout {
|
||||
gaps 16 // Gap between windows
|
||||
center-focused-column "never" // Don’t auto-center focused column
|
||||
|
||||
background-color "transparent" // <- needed for noctalia-shell to set wallpaper
|
||||
|
||||
preset-column-widths {
|
||||
proportion 0.25 // 1/4
|
||||
proportion 0.3333 // 1/3
|
||||
proportion 0.5 // 1/2
|
||||
}
|
||||
|
||||
preset-window-heights {
|
||||
proportion 0.25 // 1/4
|
||||
proportion 0.5 // 1/2
|
||||
}
|
||||
|
||||
struts {
|
||||
}
|
||||
shadow
|
||||
focus-ring {
|
||||
width 2
|
||||
|
||||
active-color "#e01b24"
|
||||
}
|
||||
border {
|
||||
off
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
layout {
|
||||
|
||||
focus-ring {
|
||||
active-color "#ff8c00"
|
||||
inactive-color "#181818"
|
||||
urgent-color "#ff8c00"
|
||||
}
|
||||
|
||||
border {
|
||||
active-color "#f43841"
|
||||
inactive-color "#181818"
|
||||
urgent-color "#f43841"
|
||||
}
|
||||
|
||||
|
||||
tab-indicator {
|
||||
active-color "#f43841"
|
||||
inactive-color "#74050b"
|
||||
urgent-color "#f43841"
|
||||
}
|
||||
|
||||
insert-hint {
|
||||
color "#f4384180"
|
||||
}
|
||||
}
|
||||
|
||||
recent-windows {
|
||||
highlight {
|
||||
active-color "#f43841"
|
||||
urgent-color "#f43841"
|
||||
}
|
||||
}
|
||||
@@ -83,8 +83,8 @@ by category, not file-by-file.
|
||||
| micro | 0 | 154 | adopt: `settings.json`, `bindings.json`; generated: `colorschemes/noctalia.micro`; bundled/state: `syntax/*` (146), `buffers/history` |
|
||||
| mpv | 0 | 0 | — (empty) |
|
||||
| nautilus | 0 | 0 | — (empty) |
|
||||
| niri | 9 | 1 | 8 live KDL includes (5 from `dotfiles/niri`, 3 overridden by the `zz-blankpad` tier, whose own 9 niri files are counted in that tier) + 1 tracked relic, see *Never track*. Box-only: `noctalia.kdl`, a generated include — [tools/dotfiles-deploy](/tools/dotfiles-deploy.md) |
|
||||
| noctalia | 3 | 2672 | Tracked and live-linked: `config.toml`, `palettes/Gruber Red.json`, `state/noctalia/settings.toml`. Everything else is app state plus noctalia's downloaded catalog: `community-palettes/` 66, `community-templates/` 80, `wallhaven/thumbs`, `clipboard/`, `plugins/`, `state.toml`, `usage_counts.json`. The 23 wallpapers #41 untracks stay on disk — the theme reads them |
|
||||
| niri | 8 | 2 | 8 live KDL includes (5 from `dotfiles/niri`, 3 overridden by the `zz-blankpad` tier, whose own 7 niri files are counted in that tier — the stale `layout.kdl` duplicate and generated `noctalia.kdl` were removed by #43) + see *Never track* for the relic. Box-only: `noctalia.kdl` (generated include, refused by `deploy.sh`) and the retired copy |
|
||||
| noctalia | 3 | 2672 | Tracked and live-linked: `config.toml`, `palettes/Gruber Red.json`, `state/noctalia/settings.toml`. Everything else is app state plus noctalia's downloaded catalog: `community-palettes/` 66, `community-templates/` 80, `wallhaven/thumbs`, `clipboard/`, `plugins/`, `state.toml`, `usage_counts.json`. The app's wallpaper library is `~/.local/share/noctalia/wallpapers` — real files it owns; the repo's duplicate copy of 23 JPEGs was untracked by #41 and deleted from this checkout by the merge, which is what untracking does in every checkout |
|
||||
| nvim | 0 | 0 | — (empty) |
|
||||
| pywalfox | 0 | 1 | `config.json` — program absent, prune |
|
||||
| qt5ct | 0 | 1 | `colors/noctalia.conf` (generated) — program absent, prune |
|
||||
|
||||
@@ -42,14 +42,18 @@ Wallpapers live at `dotfiles/noctalia/.local/share/noctalia/wallpapers/` and are
|
||||
gitignored. They were tracked until 2026-10-20 — 23 files, 80 MiB — which meant every
|
||||
clone of a *configuration* repo carried a media library no diff can review, and the
|
||||
payload only ever grows because deleting a tracked blob from the tree does not delete it
|
||||
from history. [Noctalia](noctalia-shell.md) reads
|
||||
`~/.local/share/noctalia/wallpapers/`, which is a set of symlinks into that directory, so
|
||||
the files stay on disk and untracking them broke nothing on this box.
|
||||
from history. The topology is not what one would guess: [Noctalia](noctalia-shell.md)
|
||||
keeps its own library at `~/.local/share/noctalia/wallpapers/` as a **real directory of
|
||||
real files**, so the tracked tree was a duplicate of it and never its source — measured
|
||||
2026-10-20, when the merge of #41 deleted the repo copies and the desktop did not
|
||||
flicker. Two consequences to know before untracking media: the copy that survives belongs
|
||||
to the application, not to the repo; and untracking deletes the file in every other
|
||||
checkout, `git rm --cached` on the branch notwithstanding.
|
||||
|
||||
Restore on a fresh box: every filename is `wallhaven_<id>.<ext>` and the ID is the whole
|
||||
address — `curl -L -o <name> "https://wallhaven.cc/w/<id>"` puts the library back, then
|
||||
`deploy.sh` relinks it. Losing the set is an acceptable outcome; 80 MB per clone forever
|
||||
is not.
|
||||
Restore on a fresh box: the library lives in `~/.local/share/noctalia/wallpapers/`, a path
|
||||
`deploy.sh` does not manage. Every filename is `wallhaven_<id>.<ext>` and the ID is the
|
||||
whole address — `curl -L -o <name> "https://wallhaven.cc/w/<id>"` into that directory puts
|
||||
the set back. Losing it is an acceptable outcome; 80 MB per clone forever is not.
|
||||
|
||||
## Law: never `systemctl --user enable` a unit from a worktree
|
||||
|
||||
@@ -90,3 +94,34 @@ tracked in `dotfiles/zz-blankpad/`; the pointer is not, because which desk the b
|
||||
is not a fact about the repo. A fresh box therefore starts with no output configuration —
|
||||
`config.kdl` includes `display.kdl` with `optional=true`, so niri comes up usable but with
|
||||
default monitors until `desk.sh` is run once.
|
||||
|
||||
## Law: generated theme output is never deployed
|
||||
|
||||
noctalia rewrites palette output in place — `noctalia.*`, `themes/noctalia*`,
|
||||
`colors/noctalia*`, `noctalia-*` (the classes the [census](/inventory/home-config-census.md)
|
||||
proves by same-millisecond mtime). A tracked copy of one is a stale snapshot of a colour
|
||||
scheme, and the failure is quiet: `dotfiles/zz-blankpad/niri/.config/niri/noctalia.kdl`
|
||||
(commit `b3d3df4`, 2026-10-18) sat byte-identical to the live file for two days, harmless,
|
||||
while `--check` cheerfully proposed `ADOPT … noctalia.kdl`. The moment the palette changed,
|
||||
the next sync would have quarantined the live file and restored 2026-10-18's colours —
|
||||
a tool silently reverting a user-visible theme choice.
|
||||
|
||||
`deploy.sh` now refuses the class (`SKIP … (generated theme output)`), `.gitignore` carries
|
||||
the matching patterns so it cannot be re-added, and `tests/deploy.bats` pins both
|
||||
directions: generated output is never adopted, and an ordinary identical config still is.
|
||||
Untracked in #43, Refs #42.
|
||||
|
||||
## Law: a `zz` override older than the file it overrides is a time bomb
|
||||
|
||||
Stow order means `zz-$HOST` wins, and when both packages carry the same path, sync relinks
|
||||
it *silently* — the script's own comment: "managed file changing owners between packages:
|
||||
silent relink, nothing to quarantine". `dotfiles/zz-blankpad/niri/.config/niri/cfg/layout.kdl`
|
||||
held focus ring `width 2` / `active-color "#e01b24"` from `7b7dbf5` (2026-10-17) while the
|
||||
base package absorbed `b3d3df4` (2026-10-18): `width 4`, `"#ff8c00"`. Live was linked to
|
||||
base, the newer intent, so `--check` printed `RELINK .config/niri/cfg/layout.kdl` on every
|
||||
run and one unattended `deploy.sh` would have reverted the amber focus ring with no
|
||||
quarantine and no warning. The duplicate is deleted in #43.
|
||||
|
||||
Rule: a `zz` file exists only to differ. When base absorbs the change, delete the `zz` copy
|
||||
in the same commit — and never read `RELINK` lines from `--check` as cosmetic noise; they
|
||||
are the sound of a stale override waiting for someone to run the tool for real.
|
||||
|
||||
@@ -44,6 +44,14 @@ actions=0; quarantined=0; skipped=0
|
||||
|
||||
deploy_file() { # $1 = repo file path, $2 = home-relative target path
|
||||
local src="$1" tgt="$HOME/$2"
|
||||
# Generated theme output is never deployment state: noctalia rewrites these files in
|
||||
# place, so a tracked copy is a stale snapshot and syncing one reverts a live palette to
|
||||
# the colours of the day it was committed. Classes are those the census measures by
|
||||
# mtime — see omp-docs/inventory/home-config-census.md, "Generated palette expansion".
|
||||
case "$2" in
|
||||
*/themes/noctalia*|*/colors/noctalia*|*/noctalia.*|*/noctalia-*|*/Noctalia*)
|
||||
echo "SKIP $2 (generated theme output)"; skipped=$((skipped+1)); return 0 ;;
|
||||
esac
|
||||
if [ -L "$tgt" ]; then
|
||||
if [ "$(realpath "$tgt")" = "$(realpath "$src")" ]; then return 0; fi # already deployed
|
||||
case "$MODE" in
|
||||
|
||||
Executable
+76
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bats
|
||||
# tests/deploy.bats — black-box tests for scripts/deploy.sh (Refs #42)
|
||||
#
|
||||
# Contract (see script header): the repo checkout IS the deployment; sync adopts
|
||||
# identicals, links new files, quarantines-and-replaces divergents, and stows the
|
||||
# zz-$(hostname) package last. One thing it must NEVER do is deploy generated theme
|
||||
# output: noctalia rewrites `noctalia.*` / `themes/noctalia*` in place, so a tracked
|
||||
# copy is a stale snapshot, and a sync would revert a live palette to whatever colour
|
||||
# scheme was current on the day it was committed.
|
||||
#
|
||||
# Nothing is observed except exit status, stdout, the file/symlink shape under $HOME,
|
||||
# and the quarantine directory.
|
||||
|
||||
setup() {
|
||||
TEST_TMP="$(mktemp -d)"
|
||||
REPO="$TEST_TMP/repo"
|
||||
DEPLOY="$BATS_TEST_DIRNAME/../scripts/deploy.sh"
|
||||
mkdir -p "$REPO/scripts" "$REPO/dotfiles"
|
||||
# A git repo, because the deployment checkout IS one and deploy.sh arms hooksPath.
|
||||
git init -q -b main "$REPO"
|
||||
cp "$DEPLOY" "$REPO/scripts/deploy.sh"
|
||||
chmod +x "$REPO/scripts/deploy.sh"
|
||||
export HOME="$TEST_TMP/home"
|
||||
mkdir -p "$HOME"
|
||||
export DEPLOY_REPO="$REPO" DEPLOY_HOST=box
|
||||
}
|
||||
|
||||
teardown() { rm -rf "$TEST_TMP"; }
|
||||
|
||||
# repo_file <pkg-relative path> <content>
|
||||
repo_file() { mkdir -p "$(dirname "$REPO/dotfiles/$1")"; printf '%s\n' "$2" > "$REPO/dotfiles/$1"; }
|
||||
# live_file <absolute path under $HOME> <content>
|
||||
live_file() { mkdir -p "$(dirname "$1")"; printf '%s\n' "$2" > "$1"; }
|
||||
|
||||
@test "generated theme output is never adopted into a symlink" {
|
||||
repo_file "ghostty/.config/ghostty/themes/noctalia" 'background = "#f43841"'
|
||||
live_file "$HOME/.config/ghostty/themes/noctalia" 'background = "#f43841"'
|
||||
run "$REPO/scripts/deploy.sh"
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"SKIP"*".config/ghostty/themes/noctalia"* ]]
|
||||
[ -f "$HOME/.config/ghostty/themes/noctalia" ]
|
||||
[ ! -L "$HOME/.config/ghostty/themes/noctalia" ]
|
||||
}
|
||||
|
||||
@test "a diverged live palette is never quarantined and reverted to a stale copy" {
|
||||
# The dangerous shape: the tracked file matches today's palette, the theme changes
|
||||
# tomorrow, and sync would quarantine the live file and restore the stale one.
|
||||
repo_file "niri/.config/niri/noctalia.kdl" 'active-color "#e01b24"'
|
||||
live_file "$HOME/.config/niri/noctalia.kdl" 'active-color "#ff8c00"'
|
||||
run "$REPO/scripts/deploy.sh"
|
||||
[ "$status" -eq 0 ]
|
||||
grep -q 'active-color "#ff8c00"' "$HOME/.config/niri/noctalia.kdl"
|
||||
[ ! -L "$HOME/.config/niri/noctalia.kdl" ]
|
||||
[ ! -d "$REPO/.quarantine" ]
|
||||
}
|
||||
|
||||
@test "an ordinary identical config is still adopted" {
|
||||
# Control against an exclusion so broad the tool stops deploying anything.
|
||||
repo_file "ghostty/.config/ghostty/config.ghostty" 'theme = "gruber"'
|
||||
live_file "$HOME/.config/ghostty/config.ghostty" 'theme = "gruber"'
|
||||
run "$REPO/scripts/deploy.sh"
|
||||
[ "$status" -eq 0 ]
|
||||
[ -L "$HOME/.config/ghostty/config.ghostty" ]
|
||||
[ "$(realpath "$HOME/.config/ghostty/config.ghostty")" = "$(realpath "$REPO/dotfiles/ghostty/.config/ghostty/config.ghostty")" ]
|
||||
}
|
||||
|
||||
@test "--check reports and writes nothing" {
|
||||
repo_file "ghostty/.config/ghostty/config.ghostty" 'theme = "gruber"'
|
||||
live_file "$HOME/.config/ghostty/config.ghostty" 'theme = "tokyo-night"'
|
||||
run "$REPO/scripts/deploy.sh" --check
|
||||
[ "$status" -eq 0 ]
|
||||
[[ "$output" == *"DRIFT"* || "$output" == *"QUAR"* ]]
|
||||
[ ! -L "$HOME/.config/ghostty/config.ghostty" ]
|
||||
grep -q 'theme = "tokyo-night"' "$HOME/.config/ghostty/config.ghostty"
|
||||
[ ! -d "$REPO/.quarantine" ]
|
||||
}
|
||||
Reference in New Issue
Block a user