Installed-app appearance adapter: host checkpoint
Observed 2026-09-23 from integration base 99ac8e48. The source is
tools/app_appearance.py; this checkpoint does not install or invoke it in the
normal shell service. Its input is an already prepared generation’s bounded
report.json with the pinned upstream resolver’s palette. It writes only
app-appearance/generations/ID/{terminal-foot.ini,monitor-foot.ini,coverage.json}
under private theme state. sync obtains the activation lock, reads the
acknowledged current/active pointer, and atomically updates a separate
app-appearance/active pointer for future Foot launches. It rejects foreign
generation/pointer paths and does not edit the source checkout.
The two generated Foot configs retain the portrait font and distinct app IDs,
and use resolved semantic colors plus all sixteen explicit ANSI slots. Both
Foot light and dark color sections receive the same selected palette so the
app cannot silently switch back to an unrelated default. The opt-in
osc-current command writes the pinned upstream OSC sequence only to the
caller’s own terminal stdout after an isatty and TERM=foot check; these
checks are an opt-in routing guard, not proof of the terminal executable.
It never enumerates /dev/pts,
signals a process, or closes an app. Selection and output flush hold the same
bounded activation lock as theme publication, so a newer generation cannot
publish between them. The host test compares its 21 sequences
byte-for-byte with omarchy-theme-osc on the prepared fixture.
nix/handheld-theme-command.nix now installs k230-app-appearance. The
pinned default package includes static terminal and monitor Foot configs
generated by this same adapter; tools/generate_default_foot.py --check
rejects drift. The static files extend the package output without changing
the existing palette/appearance generation identity. A fresh home can use
those Nix-store files before a mutable active pointer exists.
The host coordinator now calls app sync only after a shell commit ACK and
after releasing the activation lock. Sync reacquires that lock and checks the
expected generation, preventing a late first activation from overwriting a
newer app selection. omarchy-theme-set and the JSON chooser return an
app_appearance result separate from shell activation: applied, failed
or superseded. An app failure does not falsely roll back an acknowledged
shell theme. The host failure fixture verifies the shell pointer and phase
history stay committed when app sync fails.
Coverage is deliberately bounded: htop, nano and nnn inherit Foot’s terminal colors; mpv’s video surface is not a terminal-color consumer; Help uses shell tokens; the on-screen keyboard remains a separate shell-surface integration. Existing Foot windows are not automatically retinted, and the normal Nix launch paths still point at their earlier immutable base configs. The generated configuration and sync hook must be wired to those launch paths before task 3.4 can be marked complete. Any app reload failure belongs in a separate status from the shell generation ACK; it must not be reported as a successful existing-session recolor.
An opt-in k230-foot-session source wrapper is now packaged for Foot launch
paths. Invoked inside Foot as k230-foot-session --state-root STATE --default-generation PINNED -- COMMAND, it validates its own stdout TTY,
forks one bounded color follower, then execs COMMAND with its original args
and foreground process group. The follower inherits only that terminal’s
stdout and a Linux pidfd opened before forking for the exec’d parent. The
parent closes its copy before exec, so even a fast-exiting app cannot turn the
follower’s parent lookup into PID 1. The follower reopens its own inherited
TTY stdout through /proc/self/fd/1 as a separate nonblocking file
description; it does not change the app’s stdout flags. OSC writes have a
100 ms deadline even with a stopped/full PTY, and release the activation lock
on timeout. It polls the acknowledged
generation once per second by default, emits OSC only on a generation change
under the activation lock, uses the pinned default on rollback to no active
pointer, and exits when the parent exits. It does not scan PTYs, signal apps,
or keep a terminal open after its app leaves. If pidfd_open fails, the
requested app still execs and a fixed diagnostic identifies the missing
follower; no OSC claim is made for that session. A fake PTY with a controlling
terminal proved unchanged argument execution, foreground process group,
initial OSC, and follower exit. Separate host cases cover fast app exit and a
full PTY with injected persistent backpressure and bounded lock release. A
single 2026-09-23 x86_64 host run observed 14,600 KiB VmRSS for one Python
follower; this is neither a target RSS nor
a frame-cost measurement. The one-second polling and target cost need review
on the actual handheld. The normal Foot launchers do not yet invoke this
wrapper, so existing-window automatic recolor remains unproved in production.
python3 tests/test_handheld_app_themes.py PASS 9 host tests
python3 tests/test_handheld_theme_default.py PASS 2 host tests
python3 tests/test_omarchy_theme_transaction.py PASS 13 host tests
python3 tests/test_theme_catalog.py PASS 9 host tests
python3 tests/test_omarchy_theme_activation.py PASS 9 host tests
python3 tests/test_foot_color_session.py PASS 6 host tests; one host RSS=14,600 KiB
python3 tools/generate_default_foot.py --check PASS
python3 -m py_compile tools/app_appearance.py PASS
openspec validate the-shell-loads-omarchy-themes --strict PASS
git diff --check PASS
No Nix build, device session, physical display observation, or live Foot reload was performed for this adapter checkpoint. OpenSpec task 3.4 remains open, as do the cross-surface, app-session and physical gates.