# 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. ```text 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.