evidence · 11.6 KiB

README.md

Committed at docs/evidence/omarchy-themes/wallpaper-visibility-board-report/README.md. This is what grounds the requirements that cite it.

Open raw source

“Why don’t I ever see a background image”: QEMU root-cause investigation

UPDATE 2026-09-24, later the same day: the coordinator reproduced this on the real board with the exact real state this investigation’s first pass below could not fault (fresh generation, correct background.cache, correct appearance.json), with one difference this pass’s fixture never exercised: a real ordinary-maximized app (Terminal) was mapped. That is the missing variable. The root cause, fix, and a QEMU regression that reproduces it are in docs/evidence/omarchy-themes/ordinary-backdrop-covers-wallpaper/README.md. Everything below is the investigation that ruled out the decode/cache/ appearance-parsing path — correctly, it turns out, since none of it was at fault — kept as the record of what was checked and why this update was needed, not a conclusion to act on by itself.

Filed against the user report that catppuccin-latte’s selected background (“Color fade”, backgrounds/1-color-fade.webp) never appears — the card overview and drawer show a flat, uniform fill in latte’s own palette background colour (#eff1f5) instead of the image, on the installed board system built from master b9d892c7. This is a host + headless-QEMU result: no board, glass, or real-finger observation. Run 2026-09-24, worktree fix/wallpaper-visible-swap-jank, base master b9f81d47.

What was checked and ruled out

Traced the full path from tools/theme_activate.py’s prepare() through nix/rust-shell-client/src/appearance.rs’s snapshot loader, background_decode.rs’s decode/cache, main.rs’s draw_wallpaper(), and nix/card-shell/appearance.c/adapter.c’s deck-canvas transparency (appearance_canvas_refresh). Every step was exercised directly, not just read:

Conclusion: the current source tree does not reproduce the reported bug. Every stage of the pipeline — theme preparation, the decode cache, both appearance receivers’ JSON parsing, the deck’s canvas-transparency logic, and the compositor’s actual pixel output — was independently exercised against the real theme package and produced the correct image, not a flat fill, with byte-exact pixel matches. #eff1f5 (latte’s flat background palette colour, and the colour the user’s grim capture matched) is exactly what nix/rust-shell-client/src/render.rs’s draw_wallpaper() procedural fallback paints when wallpaper_path is None — i.e. this class of bug requires the active generation’s appearance.json to have "background": null (or the receiver to fail to resolve the active pointer at all), a state this investigation could not produce from any current, correctly prepare()d generation.

Closing a real coverage gap: the cache-hit path was previously untested end-to-end

tests/test_real_theme_paired_runtime.py’s prepare() calls never passed wallpaper_cache_tool, so every previous run of this test (including the historical docs/evidence/coherent-shell/real-theme-paired-qemu/README.md proof this investigation started from) only exercised background_decode.rs’s full-decode path, never the background.cache cache-hit path that handheld-theme-command.nix always uses on the real board (wallpaperCacheFlag, gated on coherentShell). This change adds --wallpaper-cache-tool to the test, asserts each prepared generation actually got a background.cache file, and reruns the whole paired transaction through it. Result: pixel-identical to the full-decode run (latte-deck-cache-hit.png vs latte-deck.png), confirming the cache mechanism introduced by aab56856 is not the source of the reported bug either.

One run with --wallpaper-cache-tool hit a pre-existing, documented, load-dependent flake at app_after = wait(expanded_app, 5) (the post-card_shell back app-settle wait), identical in shape to the failure docs/evidence/omarchy-themes/background-decode-cache-qemu/README.md already recorded against both a modified and an unmodified origin/master build under host load (load average: 16.69, 18.02, 19.93 on this shared dev host during this run, from concurrent unrelated agents). This is after the point where every wallpaper/cache assertion this change added had already passed (latte-deck.png was written); it is not a wallpaper regression, and matches a failure mode this repository already attributes to host CPU contention, not to this code path.

Most likely explanation for the board observation

Given the mechanism is correct in the current source end-to-end, the remaining plausible explanations are board-state, not code:

  1. A stale ~/.local/state/omarchy/current generation. Generations are “immutable, hash-identified, one-shot” (background_decode.rs’s own comment) and persist across NixOS generation switches (they are user state, not managed by the image) — exactly the kind of state AGENTS.md/openspec/config.yaml warn is never restored to preserve bring-up settings. If the board’s active pointer or a remembered generation predates a fix to the wallpaper token scheme, it would be silently reused (idempotent prepare()) instead of regenerated, since its generation hash only changes when theme_activate.py/theme_tokens.py themselves change.
  2. A stale or truncated background.cache written by a different Rust binary version than the one now installed. background.cache’s validity check (load_cached in background_decode.rs) only compares width/height/mode, not a content or code version, so a cache file produced by an earlier k230-shell-rust remains a cache hit for a later one even if the earlier binary’s encoding differed.

Board command for the coordinator

Read-only diagnosis first (does not touch the running session):

./tools/console.py /dev/ttyACM0 --wait=3 \
  "cat ~/.local/state/omarchy/current/active/report.json | grep selected_background"
./tools/console.py /dev/ttyACM0 --wait=3 \
  "cat ~/.local/state/omarchy/current/active/appearance.json | grep -o '\"background\": [^,]*'"
./tools/console.py /dev/ttyACM0 --wait=3 \
  "ls -la ~/.local/state/omarchy/current/active/background.cache"

If "background" above is null while selected_background names a still image, that generation predates or otherwise lost the wallpaper token and should be discarded and regenerated (safe: generations are a disposable cache, not durable state):

./tools/console.py /dev/ttyACM0 --wait=3 \
  "rm -rf ~/.local/state/omarchy/current/generations ~/.local/state/omarchy/current/active && \
   runuser -u shell -- k230-theme activate --json catppuccin-latte --expected-generation $(runuser -u shell -- k230-theme preview --json catppuccin-latte | python3 -c 'import json,sys; print(json.load(sys.stdin)[\"generation\"])')"

Then re-check with swaymsg card_shell enter + grim per the original board facts. If "background" is already "background" and the cache is present and sized 2,799,121 bytes, the generation is not stale and the divergence lies somewhere this investigation’s QEMU fixture cannot reach (e.g. a real-hardware-only rendering path); that would need a fresh board capture with RUST_LOG/SWAY_K230_CARD_SHELL diagnostics to progress further, which this task was directed not to run itself.

Reproduce

nix build --no-link --print-out-paths --max-jobs 1 --cores 6 \
  .#handheld-shell-rust .#card-shell .#card-composition-probe \
  .#handheld-theme-default .#handheld-theme-icons

python3 tests/test_real_theme_paired_runtime.py \
  --sway "$(nix-store -qR $(nix build --no-link --print-out-paths .#card-shell) | grep sway-unwrapped-riscv64)/bin/sway" \
  --rust "$(nix build --no-link --print-out-paths .#handheld-shell-rust)/bin/k230-shell-rust" \
  --client "$(nix build --no-link --print-out-paths .#card-composition-probe)/bin/card-composition-probe-client" \
  --theme-bundle "$(nix build --no-link --print-out-paths .#handheld-theme-default)" \
  --icons "$(nix build --no-link --print-out-paths .#handheld-theme-icons)" \
  --wallpaper-cache-tool "$NATIVE_K230_SHELL_RUST/bin/k230-shell-rust" \
  --output /tmp/k230-wj-repro

--wallpaper-cache-tool must be a build-machine-native (e.g. x86_64) binary, not the riscv64 one used for --rust: tools/theme_activate.py’s build_wallpaper_cache runs it directly with subprocess.run, no qemu wrapper, exactly like nix/handheld-theme-default/default.nix’s own wallpaperCacheTool = buildPackages.callPackage ../rust-shell-client { }. This run used /nix/store/2fy35zmwcz1yxd8cnbhs84a8q1lfiq9b-k230-shell-rust-0.1.0/bin/k230-shell-rust, an x86_64 build already resident in this host’s store from a prior handheld-theme-default build; any equivalent native build of nix/rust-shell-client works ($NATIVE_K230_SHELL_RUST above stands for that path, which is not a stable output across hosts).