aboutsummaryrefslogtreecommitdiff
path: root/todo.org
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-28 12:10:32 -0400
committerCraig Jennings <c@cjennings.net>2026-06-28 12:10:32 -0400
commite4166e0d8bbc9ed75d7ed01e9e8447401ea771c1 (patch)
tree8eaf9704e0bad57b77a06611238412b21f512918 /todo.org
parent23f8501f252342b57b59fa06a52aeb7111c4b0b3 (diff)
downloaddotemacs-e4166e0d8bbc9ed75d7ed01e9e8447401ea771c1.tar.gz
dotemacs-e4166e0d8bbc9ed75d7ed01e9e8447401ea771c1.zip
chore(todo): archive resolved tasks
Diffstat (limited to 'todo.org')
-rw-r--r--todo.org218
1 files changed, 103 insertions, 115 deletions
diff --git a/todo.org b/todo.org
index 0a067795f..a77683906 100644
--- a/todo.org
+++ b/todo.org
@@ -693,26 +693,6 @@ Ask:
Reference values -- modus-vivendi: refine-changed bg #4a4a00 fg #efef80, changed bg #363300 fg #efef80. modus-operandi: refine-changed bg #fac090 fg #553d00, changed bg #ffdfa9 fg #553d00.
Side-by-side legibility render: [[file:assets/2026-06-07-dupre-diff-face-legibility-compare.png][assets/2026-06-07-dupre-diff-face-legibility-compare.png]].
-** CANCELLED [#B] first f12 doesn't toggle the term window :bug:solo:
-CLOSED: [2026-06-25 Thu]
-Couldn't reproduce — neither could Craig (2026-06-25). The toggle code is clean (a single create-new -> ghostel on the first press, no second toggle), and the symptom pointed to an intermittent first-launch race, but with nobody able to reproduce it there's nothing to instrument. Cancelled; reopen with a live capture if it recurs.
-** DONE [#B] F12 pops EAT instead of ghostel :feature:studio:
-CLOSED: [2026-06-25 Thu]
-Done 2026-06-25, design doc =docs/design/eat-f12-toggle.org=. Part A (commit fe7aa658): F12 toggles a single EAT terminal instead of ghostel, reusing the dock-and-remember geometry toggle; ghostel stays for ai-term (M-SPC); EAT runs a plain shell with no tmux; F12 and C-; are bound in EAT's keymaps so they reach Emacs from inside the terminal. Part B (commit 687b438f): EAT's faces are exposed in theme-studio (16 named palette + attribute + prompt-annotation faces) with a =renderEatPreview=, no colors set so it stays vanilla. term 223/223, ai-term 158/158, studio gates green; the toggle wiring (F12 reaches Emacs in EAT, =(eat)= creates the buffer, the predicate recognizes it) was verified live in the daemon. Accepted tradeoff: EAT needs a buffer reload to pick up a theme switch (ghostel auto-resyncs), taken for EAT's pure-elisp face control. The visual F12 dock/toggle check is a VERIFY under Manual testing and validation.
-** DONE [#B] Consolidate on EAT, retire ghostel :feature:refactor:
-CLOSED: [2026-06-27 Sat]
-Make EAT the only terminal and remove ghostel entirely (decision 2026-06-25). Phased; the ai-term port (Phase 3) wants its own focused session with a spike first.
-- Phase 1 DONE (commit 82294404): extracted =modules/eat-config.el= (eat package + F12/C-; keymaps + the F12 dock-and-remember toggle) out of =term-config.el=. term-config keeps ghostel (ai-term's backend) and requires eat-config. Toggle tests retargeted to eat-config; full suite green.
-- Phase 2 DONE (commit 0290b015): EAT experience settings in eat-config.el -- yank-to-terminal on, directory-tracking / prompt-annotations / command-history / mouse / kill-from-terminal / alt-screen affirmed, 10MB scrollback, truecolor already on via the compiled =eat-truecolor= terminfo. zsh shell-integration source line added to =~/.dotfiles/common/.zshrc= (uncommitted -- needs a dotfiles commit + a pull on the other daily driver).
-- F12 = eshell-through-EAT (2026-06-25, commits cbd38d88 + c99fad28): F12 now opens eshell run through EAT (eat-eshell-mode) instead of a standalone EAT zsh shell, so the primary terminal is eshell (elisp functions as commands, TRAMP transparency) with EAT rendering visual commands. Retired eshell-toggle + xterm-color; added a zsh-parity prompt (git branch + [N] exit status) and a zoxide =z= sharing the zsh database. eat-config + eshell-config kept separate.
-- Phase 3 DONE (commit 6c8f2a9c): ported ai-term from ghostel to EAT. The spike confirmed EAT + tmux detach/reattach behaves exactly like ghostel + tmux (eat spawns, sends =tmux new-session -A -s aiv-<project>=; killing the buffer leaves the session alive; respawn reattaches). The coupling was far smaller than feared -- most of the ~30 refs were comments, and agent detection is name-based ("agent [...]"), so backend-agnostic. Swaps: =(ghostel)= -> =(eat)= with =eat-buffer-name=, =ghostel-send-string= -> a process-send-string helper, M-SPC bound directly in =eat-semi-char-mode-map= (no exception/rebuild dance). 157 ai-term tests green. Real-agent launch + detach/reattach is a VERIFY under Manual testing and validation.
-- Phase 4 DONE (commit 6a9ec62e): retired ghostel. Migrated the terminal-generic keepers into eat-config -- the tmux copy-mode (=C-<up>= enters it, same UX + keybinding; agents run EAT over tmux so it's still tmux's own copy-mode) and the tmux-history capture, swapping =ghostel-send-string= -> a pty write and the mode checks -> eat-mode. Repointed the dashboard "Launch Terminal" to =cj/term-toggle=, swapped the =face-diagnostic= terminal-mode check to eat-mode, refreshed the auto-dim comment. Deleted =term-config.el= + its init require. EAT's default =eat-semi-char-non-bound-keys= already lets windmove / buffer-move / Emacs keys reach the terminal, so no exception-list port was needed. Tests retargeted (tmux-history 15/15). The copy-mode + tmux-history live check is a VERIFY under Manual testing and validation.
-- Phase 5 DONE (commit eb4aa232): removed the theme-studio ghostel app (=GHOSTEL_FACES=) now those faces are dead (ansi-color stays -- EAT inherits it), =package-delete='d the ghostel ELPA package, and swept the remaining ghostel mentions in comments/docs. The optional F8/F10 surfacing in agent buffers was not pursued.
-
-** DONE [#C] ai-term.el commentary names a stale F9 keybinding scheme :quick:solo:
-CLOSED: [2026-06-28 Sun]
-Rewrote the header Commentary (the "four global keys" + "Four F-key entry points: F9 / C-F9 / s-F9 / M-F9" block) to the current scheme: the =C-; a= prefix map (a = toggle, s = select/launch, n = next, k = kill) plus the global =M-SPC= -> =cj/ai-term-next=. Fixed the binding-claiming docstrings (=cj/ai-term=, =cj/ai-term-pick-project=, =cj/ai-term-shutdown=, =cj/--ai-term-dispatch=) and swept the behavioral =F9= shorthand + two stray =C-F9= claims in internal docstrings. Kept the two historical incident names ("F9 shrink bug", "F9 shows another agent" bug) as recorded references. Doc-only: parens ok, no new byte-compile warnings, dispatch tests 4/4, live-reloaded.
-
** TODO [#B] org-capture popup leaks f12 / f10 / f11 / ai-term keys :bug:
While the org-capture popup is open, the global F-keys (the =f12= term, =f10= / =f11=, the ai-term family) still fire and pop a terminal over the capture. Disable those keys for the duration of the capture popup if there's a clean way. Research first and report; if it's too invasive, defer or cancel rather than force it. From the roam inbox 2026-06-24.
** TODO [#B] VAMP — extract music-config into a standalone player :feature:refactor:
@@ -725,16 +705,6 @@ Brainstorm complete 2026-06-22 — validated design at [[file:docs/design/vamp-m
Next: (1) revise the spec to the new direction; (2) spike the risky assumptions (mpd dumb-single-file-player contract; m3u =.desktop= on Hyprland); (3) =/start-work= against the revised spec — pure-helper extraction (review Migration Plan step 1) is the safe first phase. Priority [#C] is a placeholder pending Craig's call.
-** CANCELLED [#C] ai-term: fullscreen on summon, toggle when only one agent :feature:
-CLOSED: [2026-06-28 Sun]
-Cancelled — rethinking the ai-term window behavior before committing to an approach.
-Two behaviors for M-SPC (=cj/ai-term-next=) when bringing up an agent from a non-agent context:
-1. Summon fullscreen. Today the agent opens in its deliberate dock — 0.75 frame height on a laptop (=cj/ai-term-laptop-height=), a width fraction on desktop (=cj/--ai-term-default-size= / =-default-direction=) — so summoning from another buffer lands it at partial size and Craig hits =C-x 1= to fill the frame. When summoning (no agent window currently shown), open it fullscreen instead.
-2. Single-agent toggle. When exactly one agent exists, M-SPC currently self-cycles (=cj/--ai-term-next-agent-dir= wraps to the same dir, re-selecting the same window — a no-op). Repurpose it: with one agent, M-SPC toggles the window (show fullscreen if hidden, hide if shown), matching =cj/ai-term= (C-; a a). With 2+ agents, keep the cycle behavior.
-Touches the dock/toggle state machine (=cj/--ai-term-last-direction= / =-size= / =-was-bury= etc.), so implement carefully and keep the multi-agent cycle plus toggle-restore reversibility intact. From the roam inbox.
-** DONE [#C] dashboard: no icon for URL bookmarks :bug:
-CLOSED: [2026-06-28 Sun]
-Resolved by dropping per-item list icons rather than adding a URL glyph: set =dashboard-set-file-icons nil= (=dashboard-config.el=), so recents / bookmarks / projects render without leading icons and URL vs file bookmarks look uniform. Section-heading icons and the launcher row keep theirs. Verified live: the =*dashboard*= buffer's bookmark/project items render glyph-free (the URL bookmark "Tensegrity Blog" now matches the file bookmarks). From the roam inbox.
** TODO [#C] dashboard: add a weather entry (wttrin) with an icon :feature:
Put weather on the dashboard with a good illustrative icon, sourced from wttrin. Build on the release/0.4.0 wttrin now loaded via =:load-path= in =weather-config.el= (it carries the mode-line weather string plus auto-fit). Decide format (a current temp/conditions line vs a small forecast), refresh cadence, and placement/icon. From the roam inbox.
** TODO [#C] nov: sepia reading view (dark bg, tan/sepia text) :feature:
@@ -2433,35 +2403,6 @@ configuration (=text-config=, =diff-config=, =ledger-config=,
=games-config=, =mu4e-org-contacts-setup=, =telega-config=,
=httpd-config=, =org-agenda-config-debug=).
-** DONE [#B] calendar-sync robustness: atomic writes, curl --fail, zero-event false errors :bug:solo:next:
-CLOSED: [2026-06-25 Thu]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-20
-:END:
-All three landed (commit 11049db5) and verified against the live feed — gcal/pcal/dcal all fetched and wrote cleanly. (1) =calendar-sync--write-file= and =--save-state= write a temp file in the same directory then =rename-file= it into place, so a mid-write reader never sees a partial calendar. (2) Both curl fetches got =--fail=, so an HTTP 404/500 page exits non-zero instead of flowing its HTML into conversion. (3) =--parse-ics= now distinguishes a healthy zero-event calendar (real =BEGIN:VCALENDAR=, no in-window events -> header) from garbage (no VCALENDAR -> nil), so near-empty calendars no longer report "parse failed". New robustness tests + the empty-calendar boundary test corrected; calendar-sync suite 575/575.
-
-** DONE [#B] org-roam :config triggers the 15-20s refile scan synchronously at first idle :bug:solo:next:
-CLOSED: [2026-06-25 Thu]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-20
-:END:
-Fixed (commit 4e48432c): removed the redundant =cj/build-org-refile-targets= call from org-roam's :config (=org-roam-config.el=). org-roam is =:defer 1=, so that call ran the multi-file refile scan synchronously at the 1s idle on a cold cache, freezing Emacs at first idle; =org-refile-config.el= already schedules the same build on a 5s idle timer, so it was a duplicate. The first-idle freeze is gone. This removed the *duplicate* early scan, not the scan's cost — making the scan itself faster stays the separate =[#B] Optimize org-capture target building performance= task (profile-first).
-
-** DONE [#B] transcription: stderr never reaches the log :bug:solo:next:
-CLOSED: [2026-06-28 Sun]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-25
-:END:
-The "/tmp" half was DONE earlier (commit 3d9a650d): video transcripts land beside the source video via an =output-base= threaded through =cj/--start-transcription-process=.
-The stderr half is now fixed: =make-process :stderr= had a file PATH, which Emacs turns into a phantom buffer named after the path — so the error text never reached the log and a buffer leaked per run. Fix (2026-06-28): an explicit, erased stderr buffer (=" *transcribe-stderr-<file>*"=) is passed to =:stderr=, threaded to the sentinel, drained into the log file via =cj/--append-to-log=, then killed. Keeping stderr off the stdout =:buffer= leaves the transcript clean. TDD: new =test-tx-start-process-stderr-is-a-buffer-not-a-path= plus strengthened sentinel tests asserting the stderr text reaches the log and the buffer is killed; full transcription suite 39/39 green; live-reloaded.
-Live failing-run confirmation is filed under "Manual testing and validation".
-
-** DONE [#B] eww User-Agent advice may not inject under Emacs 30 :bug:
-CLOSED: [2026-06-25 Thu]
-Root cause was NOT =derived-mode-p= (that works in both batch and the daemon — my initial guess was wrong). It was the lexical-binding special-var trap: =eww-config.el= is =lexical-binding: t= and the advice =my-eww--inject-user-agent= let-binds url.el's =url-request-extra-headers=, but the file never declared that var special. The byte-compiler bound it lexically, so the injected User-Agent never reached =url-retrieve= and the desktop UA silently dropped in compiled production (eww still worked, just with the default UA). Verified: the byte-compiled advice returned nil before, =t= after. Fix (commit 6131da8e): a top-level =(defvar url-request-extra-headers)= so the compiler treats it as dynamic and the binding propagates. All 3 advice tests pass; live-reloaded. Same class as the json-object-type and the LSP-test special-var traps — a foreign special var let-bound in a lexical file always needs a compile-time defvar/require.
-** DONE [#B] calendar-sync: a declined single occurrence keeps :STATUS: accepted :bug:solo:
-CLOSED: [2026-06-25 Thu]
-A recurring event declined for just one occurrence synced out with =:STATUS: accepted= (chime then faithfully showed it). Root cause (diagnosed by a chime session, 2026-06-24): =calendar-sync--apply-single-exception= merged the override's =:attendees= but never re-derived =:status=, so the occurrence kept the series master's accepted status, and =calendar-sync--filter-declined= (which keys off =:status=) didn't drop it. Fix (TDD): in =apply-single-exception=, when overriding =:attendees=, re-derive =:status= via =calendar-sync--find-user-status= against =calendar-sync-user-emails=. Four new tests in =test-calendar-sync--apply-single-exception.el= (declined → "declined"; no-attendee override → inherited intact; accepted override → accepted; override without the user → inherited intact); recurrence + find-user-status + integration suites unchanged. Live-reloaded; a manual re-sync ran clean. The specific 2026-06-24 Arusyak occurrence is past now (its RECURRENCE-ID override aged out of the feed), so the live confirmation lands on the next single-occurrence decline.
** TODO [#C] Unified popup and messenger UX — placement, dismissal, one library :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-20
@@ -3004,13 +2945,6 @@ Tie this into the existing coverage work:
:END:
Spec: [[id:540bf06b-16b8-46c6-b459-c40d1b9c795d][keybinding-console-safety-spec-doing.org]]. Phase 0 (revert 4a1ecf64) is done and pushed. Decisions D1-D5 are open TODOs in the spec; D2/D4/D5 gate the primary work (Phase 1 prune via Appendix D, Phase 2 consolidate + retire the translation block), while D1/D3 (the console-safe prefix) gate only the optional Phase 3 and can stay open indefinitely. Resolve D2/D4/D5, then run Phase 1-2. Appendix D is the keybinding pruning checklist. Add a =#+TODO: TODO | DONE SUPERSEDED CANCELLED= header line to the spec if adopting those decision keywords (rulesets convention update, 2026-06-12).
-** DONE [#C] ledger-config is orphaned — ledger-mode never configured :bug:quick:
-CLOSED: [2026-06-28 Sun]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-21
-:END:
-Resolved by wiring (Craig's call 2026-06-28: wire it, not delete). Already landed in commit 6ec857ae (2026-06-24, "feat(ledger): un-orphan ledger-config and rewrite clean-on-save") — =init.el:123= now requires =ledger-config=, after this task's 2026-06-21 review, so the task was stale. Confirmed it loads cleanly; the =cj/executable-find-or-warn "ledger"= guard is in its =:config=. Follow-on audit + guardrail-UX work filed as its own task below.
-
** TODO [#C] Ledger-config audit + guardrail UX :feature:
Now that =modules/ledger-config.el= is wired (6ec857ae), audit it thoroughly — correctness first, then especially the data-entry UX. Goal (Craig): a workflow with enough guardrails that it's hard to make a costly mistake in a financial file. Audit surface: clean-on-save behavior (does =ledger-mode-clean-buffer= ever reorder/rewrite in a surprising way; is the demoted-error swallow hiding real problems), flycheck-ledger coverage (unbalanced transactions, bad account names) and whether it surfaces clearly, reconcile safety, the report set, company-ledger account/payee completion as a typo guard, and any add-transaction entry flow. Identify gaps, then design the guardrails (validation on save, completion to prevent account-name drift, a confirm before destructive reconcile, etc.). The correctness/gap audit can run solo; the UX guardrail choices need Craig's preferences, so not tagged :solo:. Priority [#C] is a placeholder — bump if ledger becomes active daily use.
@@ -3837,45 +3771,6 @@ v1 (read-only) implemented and tested (Phases 1-3): the gkeepapi Python bridge (
Next: v2 (read-write — create/edit back to Keep, with a staleness guard) per the spec, the immediate follow-on once the live read is confirmed. Later: list/checkbox rendering, package extraction.
Spec: [[file:docs/specs/google-keep-emacs-integration-spec.org][google-keep-emacs-integration-spec.org]] (Ready, 2 review rounds; all five decisions resolved 2026-06-25).
Offline (from the roam inbox, "pocketbook" framing): make the integration usable without network — a local cache of fetched notes for offline read, and queued writes replayed on reconnect. Folds the standalone "a Google Keep integration that works offline" idea into v2+ scope.
-** DONE [#C] Remove unused system-power keybindings :refactor:quick:solo:
-CLOSED: [2026-06-28 Sun]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-20
-:END:
-Removed the per-command leaf keys (s/r/e/l/L/E/S) under =C-; != and the prefix map. =C-; != now binds directly to =cj/system-command-menu= (the completing-read menu), via =cj/register-command=, so every command stays reachable through the menu and the leaf keys are freed. Updated the module commentary and the two keymap tests (now assert the direct-command binding + no submap; the menu-dispatch test already covers reachability). 2/2 keymap + 18/18 system-cmd tests green; live-reloaded; confirmed =C-; != resolves to =cj/system-command-menu=. Decision was Craig's (2026-06-28, cj comment): remove them all.
-
-** CANCELLED [#C] dirvish image previews missing in the pictures dir :bug:
-CLOSED: [2026-06-25 Thu]
-Craig couldn't reproduce — image previews render fine in dirvish now. Cancelled.
-** DONE [#C] ai-term test isolation: collapse-split leaks state breaking display-rule :bug:test:
-CLOSED: [2026-06-25 Thu]
-Root cause found: the display rule's 4th action =cj/--ai-term-display-saved= splits per the globals =cj/--ai-term-last-direction= / =cj/--ai-term-last-size=, captured on the last toggle-off. The collapse-split multi-window and single-window tests call =cj/ai-term= (which captures those globals) but only let-bound =cj/--ai-term-last-was-bury=, so they leaked =last-direction= = below into =display-rule=, which then split below (left-col 0) instead of right (left-col 40). Confirmed by instrumenting: every window/split global was identical fresh vs after-collapse, but the leaked =last-direction= flipped the directional split. Fix: let-bind =cj/--ai-term-last-direction= + =cj/--ai-term-last-size= to nil in both collapse-split tests, isolating the capture-state globals the way the roundtrip test in the same file already does. Full ai-term suite now 158/158 green.
-** DONE [#C] dirvish leaves stray buffers; should be single-instance like org-capture :bug:
-CLOSED: [2026-06-25 Thu]
-Diagnosed and fixed (commit e190648b). The single-instance frame behavior already existed (=cj/dirvish-popup-focus-existing= raises the open popup on a second Super+F; =q= tears it down). Measured the litter: navigating a dirvish session piles up one dired buffer per directory (2 -> 6 over three subdirs), but =dirvish-quit= reaps them all back to baseline. So the leak was only when the popup is closed WITHOUT =q= — closing the Hyprland float or losing focus bypassed =dirvish-quit= and orphaned the session's buffers. Fix: a =delete-frame-functions= hook scoped to the "dirvish" popup frame runs =dirvish-quit= on every close path (verified: navigated session drops back to baseline on frame close without q). Deliberately did NOT enable =dired-kill-when-opening-new-dired-buffer= — it's off on purpose (dirvish-config.el:208) because it breaks mark-in-A-then-move-to-B; the popup-scoped reap leaves regular =C-x d= sessions and that workflow untouched.
-** DONE [#C] ai-term: step between running ai-terms even when detached :feature:solo:next:
-CLOSED: [2026-06-25 Thu]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-22
-:END:
-Implemented 2026-06-25 (commit 79cbccb5): =cj/ai-term-next= now cycles every active agent (a live buffer OR a live tmux session) keyed on the project dir and ordered by buffer name, and stepping onto a detached one attaches it (=cj/--ai-term-show-or-create= recreates the terminal, which reattaches the session). New pure helpers =cj/--ai-term-next-agent-dir= + =cj/--ai-term-active-agent-dirs= with 10 ERT tests; the live-buffer swap path is unchanged. Live check filed under Manual testing and validation.
-
-** DONE [#C] Compare terminal themeability: EAT vs vterm vs ghostel :feature:solo:next:
-CLOSED: [2026-06-25 Thu]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-22
-:END:
-Researched 2026-06-24. All three expose the 16 ANSI colors as Emacs faces (=eat-term-color-*=, =vterm-color-*=, =ghostel-color-*=, each inheriting =ansi-color-*= / =term-color-*=). Ghostel is the most live-themeable: it alone registers an =enable-theme-functions= resync hook (repaints live buffers on a theme change) and exposes a dedicated =ghostel-default= face for the terminal's default fg/bg. EAT (pure elisp, where the faces are the real render source) and vterm (native, faces read at render) both expose themeable palettes but need a buffer reload to pick up a theme switch and give less default-fg/bg control. Outcome: Craig is moving F12 to EAT anyway, for pure-elisp face control and the fun of theming it — see "F12 pops EAT instead of ghostel" above, which carries the resync tradeoff knowingly.
-
-** CANCELLED [#D] Un-pin ghostel from 0.33.0 once upstream fixes #422/#423 :bug:
-CLOSED: [2026-06-26 Fri 04:56]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-20
-:END:
-ghostel is held at 0.33.0 (=ghostel-20260604.2049=, commit 5779a2adceb2) in =modules/term-config.el= to dodge the 0.35.x native-PTY crash. When dakra/ghostel ships a fix for #422 (Linux malloc/signal reentrancy) and #423 (macOS recursive lock), restore =:ensure t= (drop the pin comment) and =package-upgrade ghostel=, then re-run the open-ghostel-in-a-GUI-frame survival check. Watch the two issues for the fixing commit.
-
-archsetup automated the zig 0.15.2 pin (managed =install_zig_pin= step, sha-verified, unit-tested). If the un-pinned ghostel bumps its ghostty dependency to a newer zig, send archsetup the new version + sha256 so it bumps its =ZIG_VERSION= / =ZIG_SHA256= constants (=inbox-send archsetup=).
-
** TODO [#D] Slack message buffers in a reused popup window :quick:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-25
@@ -4188,11 +4083,6 @@ Three small reveal.js improvements; collected into one task because each on its
** TODO [#D] Treesitter grammar offline cache :feature:
Treesitter grammars are downloaded by =treesit-auto= on first use and live outside the localrepo. For true offline reproducibility, cache the grammars next to the localrepo (a =.localrepo/treesitter/= tier, or a separate mirror script). Cross-linked from =docs/design/localrepo.org=.
-** DONE [#C] EAT diff green and red too bright :quick:
-CLOSED: [2026-06-28 Sun]
-Darkened the added/removed line backgrounds. Added =eat-term-color-22= (added green) and =eat-term-color-52= (removed red) to the eat section of =scripts/theme-studio/WIP.json= at about half their former brightness — =#005F00= -> =#002f00=, =#5F0000= -> =#2f0000= (Craig: pick an appropriate darkness from WIP.json; halved, symmetric, still clearly green/red). Regenerated =themes/WIP-theme.el= via build-theme.el and re-applied the WIP theme live in the daemon (confirmed: =eat-term-color-22= = #002f00, =-52= = #2f0000). EAT uses each face's :foreground as the palette value for both fg and bg paint, so darkening the foreground darkens the diff background.
-Scope notes: (1) the green index (22) was confirmed via =C-h F=; the red (52) is the symmetric ANSI-256 dark-diff counterpart — if removed lines don't darken, the real index needs a live sample. (2) Only the line backgrounds are themed; the brighter within-line word-highlight shades are different (unconfirmed) indices, left for a live sample if Craig still finds them bright. (3) A 256-cube override is global (hits every terminal program emitting color 22/52). (4) Studio round-trip caveat: a future studio re-export may drop these cube faces until the studio formally tracks them. Visual darkness confirm filed under "Manual testing and validation".
-
** TODO [#C] Webm previews not rendering in dirvish :bug:solo:
Dirvish computes a valid MD5-hashed cache path and =ffmpegthumbnailer= thumbnails the webm fine by hand, but dirvish's async cache generation never lands the jpg, so no preview shows. Root-cause the async step: trace the =(cache . CMD)= recipe dispatch and sentinel (=dirvish-shell-preview-proc-s=, =dirvish--make-proc=) to find why the generated jpg is never written or displayed.
@@ -4208,11 +4098,6 @@ Claude Code truncates a colored span without a reset, so the color bleeds down t
** TODO [#D] occur/xref font-lock coloring watch :bug:
=occur= and =xref= enable font-lock themselves, not via =global-font-lock-mode=, so the exclusion fix does not apply and they show source-line fontification on purpose. No action unless a result ever renders with colors that do not match its source buffer, in which case investigate the real mechanism.
-** DONE [#B] eat semi-char mode swallows zoom-out :bug:solo:
-CLOSED: [2026-06-27 Sat]
-Shipped in commit 69fee81f: =eat-semi-char-mode-map= now binds =C--= -> =text-scale-decrease= and =C-0= -> =cj/eat-text-scale-reset= (=eat-config.el:495-496=). Original report below.
-In =eat-semi-char-mode= (the AI session buffers) =C--= is bound to =eat-self-input= and forwarded to the terminal, so it never reaches =text-scale-decrease= and the font can only grow. On velox 2026-06-27 a session climbed to text-scale 17 (~20x, unreadable) with no in-buffer way down. Fix (binding in =eat-semi-char-mode-map= works for eat, unlike ghostel): =C--= to =text-scale-decrease=, =C-0= to =(text-scale-set 0)=. Tradeoff: =C--= no longer forwarded to the terminal (Claude TUI and tmux do not use it), =C-0= shadows =digit-argument= inside eat buffers only. From the home-emacs inbox handoff 2026-06-27. Roam KB 799e8ab5-1c2c-4874-9abc-dff2ec354181.
-
* Emacs Someday/Maybe
** TODO [#D] GPTel orphan tasks and useful ideas :feature:
@@ -9226,3 +9111,106 @@ Files involved: =modules/tramp-config.el=, =modules/dirvish-config.el=.
Remote dates/sizes don't come from the dired =ls= listing or =dired-listing-switches=. They come from =dirvish-data-for-dir= (=dirvish-tramp.el:95=), which runs =ls -1lahi= on the remote and parses the columns into the attribute cache. That method only fires when both =(dirvish-prop :remote-async)= is a number and =(dirvish-prop :gnuls)= is a string. When either gate is shut, dirvish falls back to its default, which deliberately skips =(file-attributes f-name)= for remote files (=dirvish.el:904=, a perf guard) — leaving attrs nil, so the file-size and file-time widgets render "?" (=dirvish-widgets.el:216,247=).
That explains why every prior fix missed: dired-listing-switches feed a different code path entirely, and disabling =tramp-direct-async-process= shuts the =:remote-async= gate, which is the one path that populates remote attributes — exactly backwards. The config already enables direct-async for ssh/sshx (=tramp-config.el:79-88=), so the remaining closed gate is per-host: =:gnuls= (no GNU ls on FreeBSD-based truenas) or direct-async not taking effect for the method. Could not verify on a live remote from the work session — handed the per-host diagnostics up into the task body.
+** CANCELLED [#B] first f12 doesn't toggle the term window :bug:solo:
+CLOSED: [2026-06-25 Thu]
+Couldn't reproduce — neither could Craig (2026-06-25). The toggle code is clean (a single create-new -> ghostel on the first press, no second toggle), and the symptom pointed to an intermittent first-launch race, but with nobody able to reproduce it there's nothing to instrument. Cancelled; reopen with a live capture if it recurs.
+** DONE [#B] F12 pops EAT instead of ghostel :feature:studio:
+CLOSED: [2026-06-25 Thu]
+Done 2026-06-25, design doc =docs/design/eat-f12-toggle.org=. Part A (commit fe7aa658): F12 toggles a single EAT terminal instead of ghostel, reusing the dock-and-remember geometry toggle; ghostel stays for ai-term (M-SPC); EAT runs a plain shell with no tmux; F12 and C-; are bound in EAT's keymaps so they reach Emacs from inside the terminal. Part B (commit 687b438f): EAT's faces are exposed in theme-studio (16 named palette + attribute + prompt-annotation faces) with a =renderEatPreview=, no colors set so it stays vanilla. term 223/223, ai-term 158/158, studio gates green; the toggle wiring (F12 reaches Emacs in EAT, =(eat)= creates the buffer, the predicate recognizes it) was verified live in the daemon. Accepted tradeoff: EAT needs a buffer reload to pick up a theme switch (ghostel auto-resyncs), taken for EAT's pure-elisp face control. The visual F12 dock/toggle check is a VERIFY under Manual testing and validation.
+** DONE [#B] Consolidate on EAT, retire ghostel :feature:refactor:
+CLOSED: [2026-06-27 Sat]
+Make EAT the only terminal and remove ghostel entirely (decision 2026-06-25). Phased; the ai-term port (Phase 3) wants its own focused session with a spike first.
+- Phase 1 DONE (commit 82294404): extracted =modules/eat-config.el= (eat package + F12/C-; keymaps + the F12 dock-and-remember toggle) out of =term-config.el=. term-config keeps ghostel (ai-term's backend) and requires eat-config. Toggle tests retargeted to eat-config; full suite green.
+- Phase 2 DONE (commit 0290b015): EAT experience settings in eat-config.el -- yank-to-terminal on, directory-tracking / prompt-annotations / command-history / mouse / kill-from-terminal / alt-screen affirmed, 10MB scrollback, truecolor already on via the compiled =eat-truecolor= terminfo. zsh shell-integration source line added to =~/.dotfiles/common/.zshrc= (uncommitted -- needs a dotfiles commit + a pull on the other daily driver).
+- F12 = eshell-through-EAT (2026-06-25, commits cbd38d88 + c99fad28): F12 now opens eshell run through EAT (eat-eshell-mode) instead of a standalone EAT zsh shell, so the primary terminal is eshell (elisp functions as commands, TRAMP transparency) with EAT rendering visual commands. Retired eshell-toggle + xterm-color; added a zsh-parity prompt (git branch + [N] exit status) and a zoxide =z= sharing the zsh database. eat-config + eshell-config kept separate.
+- Phase 3 DONE (commit 6c8f2a9c): ported ai-term from ghostel to EAT. The spike confirmed EAT + tmux detach/reattach behaves exactly like ghostel + tmux (eat spawns, sends =tmux new-session -A -s aiv-<project>=; killing the buffer leaves the session alive; respawn reattaches). The coupling was far smaller than feared -- most of the ~30 refs were comments, and agent detection is name-based ("agent [...]"), so backend-agnostic. Swaps: =(ghostel)= -> =(eat)= with =eat-buffer-name=, =ghostel-send-string= -> a process-send-string helper, M-SPC bound directly in =eat-semi-char-mode-map= (no exception/rebuild dance). 157 ai-term tests green. Real-agent launch + detach/reattach is a VERIFY under Manual testing and validation.
+- Phase 4 DONE (commit 6a9ec62e): retired ghostel. Migrated the terminal-generic keepers into eat-config -- the tmux copy-mode (=C-<up>= enters it, same UX + keybinding; agents run EAT over tmux so it's still tmux's own copy-mode) and the tmux-history capture, swapping =ghostel-send-string= -> a pty write and the mode checks -> eat-mode. Repointed the dashboard "Launch Terminal" to =cj/term-toggle=, swapped the =face-diagnostic= terminal-mode check to eat-mode, refreshed the auto-dim comment. Deleted =term-config.el= + its init require. EAT's default =eat-semi-char-non-bound-keys= already lets windmove / buffer-move / Emacs keys reach the terminal, so no exception-list port was needed. Tests retargeted (tmux-history 15/15). The copy-mode + tmux-history live check is a VERIFY under Manual testing and validation.
+- Phase 5 DONE (commit eb4aa232): removed the theme-studio ghostel app (=GHOSTEL_FACES=) now those faces are dead (ansi-color stays -- EAT inherits it), =package-delete='d the ghostel ELPA package, and swept the remaining ghostel mentions in comments/docs. The optional F8/F10 surfacing in agent buffers was not pursued.
+** DONE [#C] ai-term.el commentary names a stale F9 keybinding scheme :quick:solo:
+CLOSED: [2026-06-28 Sun]
+Rewrote the header Commentary (the "four global keys" + "Four F-key entry points: F9 / C-F9 / s-F9 / M-F9" block) to the current scheme: the =C-; a= prefix map (a = toggle, s = select/launch, n = next, k = kill) plus the global =M-SPC= -> =cj/ai-term-next=. Fixed the binding-claiming docstrings (=cj/ai-term=, =cj/ai-term-pick-project=, =cj/ai-term-shutdown=, =cj/--ai-term-dispatch=) and swept the behavioral =F9= shorthand + two stray =C-F9= claims in internal docstrings. Kept the two historical incident names ("F9 shrink bug", "F9 shows another agent" bug) as recorded references. Doc-only: parens ok, no new byte-compile warnings, dispatch tests 4/4, live-reloaded.
+** CANCELLED [#C] ai-term: fullscreen on summon, toggle when only one agent :feature:
+CLOSED: [2026-06-28 Sun]
+Cancelled — rethinking the ai-term window behavior before committing to an approach.
+Two behaviors for M-SPC (=cj/ai-term-next=) when bringing up an agent from a non-agent context:
+1. Summon fullscreen. Today the agent opens in its deliberate dock — 0.75 frame height on a laptop (=cj/ai-term-laptop-height=), a width fraction on desktop (=cj/--ai-term-default-size= / =-default-direction=) — so summoning from another buffer lands it at partial size and Craig hits =C-x 1= to fill the frame. When summoning (no agent window currently shown), open it fullscreen instead.
+2. Single-agent toggle. When exactly one agent exists, M-SPC currently self-cycles (=cj/--ai-term-next-agent-dir= wraps to the same dir, re-selecting the same window — a no-op). Repurpose it: with one agent, M-SPC toggles the window (show fullscreen if hidden, hide if shown), matching =cj/ai-term= (C-; a a). With 2+ agents, keep the cycle behavior.
+Touches the dock/toggle state machine (=cj/--ai-term-last-direction= / =-size= / =-was-bury= etc.), so implement carefully and keep the multi-agent cycle plus toggle-restore reversibility intact. From the roam inbox.
+** DONE [#C] dashboard: no icon for URL bookmarks :bug:
+CLOSED: [2026-06-28 Sun]
+Resolved by dropping per-item list icons rather than adding a URL glyph: set =dashboard-set-file-icons nil= (=dashboard-config.el=), so recents / bookmarks / projects render without leading icons and URL vs file bookmarks look uniform. Section-heading icons and the launcher row keep theirs. Verified live: the =*dashboard*= buffer's bookmark/project items render glyph-free (the URL bookmark "Tensegrity Blog" now matches the file bookmarks). From the roam inbox.
+** DONE [#B] calendar-sync robustness: atomic writes, curl --fail, zero-event false errors :bug:solo:next:
+CLOSED: [2026-06-25 Thu]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-20
+:END:
+All three landed (commit 11049db5) and verified against the live feed — gcal/pcal/dcal all fetched and wrote cleanly. (1) =calendar-sync--write-file= and =--save-state= write a temp file in the same directory then =rename-file= it into place, so a mid-write reader never sees a partial calendar. (2) Both curl fetches got =--fail=, so an HTTP 404/500 page exits non-zero instead of flowing its HTML into conversion. (3) =--parse-ics= now distinguishes a healthy zero-event calendar (real =BEGIN:VCALENDAR=, no in-window events -> header) from garbage (no VCALENDAR -> nil), so near-empty calendars no longer report "parse failed". New robustness tests + the empty-calendar boundary test corrected; calendar-sync suite 575/575.
+** DONE [#B] org-roam :config triggers the 15-20s refile scan synchronously at first idle :bug:solo:next:
+CLOSED: [2026-06-25 Thu]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-20
+:END:
+Fixed (commit 4e48432c): removed the redundant =cj/build-org-refile-targets= call from org-roam's :config (=org-roam-config.el=). org-roam is =:defer 1=, so that call ran the multi-file refile scan synchronously at the 1s idle on a cold cache, freezing Emacs at first idle; =org-refile-config.el= already schedules the same build on a 5s idle timer, so it was a duplicate. The first-idle freeze is gone. This removed the *duplicate* early scan, not the scan's cost — making the scan itself faster stays the separate =[#B] Optimize org-capture target building performance= task (profile-first).
+** DONE [#B] transcription: stderr never reaches the log :bug:solo:next:
+CLOSED: [2026-06-28 Sun]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-25
+:END:
+The "/tmp" half was DONE earlier (commit 3d9a650d): video transcripts land beside the source video via an =output-base= threaded through =cj/--start-transcription-process=.
+The stderr half is now fixed: =make-process :stderr= had a file PATH, which Emacs turns into a phantom buffer named after the path — so the error text never reached the log and a buffer leaked per run. Fix (2026-06-28): an explicit, erased stderr buffer (=" *transcribe-stderr-<file>*"=) is passed to =:stderr=, threaded to the sentinel, drained into the log file via =cj/--append-to-log=, then killed. Keeping stderr off the stdout =:buffer= leaves the transcript clean. TDD: new =test-tx-start-process-stderr-is-a-buffer-not-a-path= plus strengthened sentinel tests asserting the stderr text reaches the log and the buffer is killed; full transcription suite 39/39 green; live-reloaded.
+Live failing-run confirmation is filed under "Manual testing and validation".
+** DONE [#B] eww User-Agent advice may not inject under Emacs 30 :bug:
+CLOSED: [2026-06-25 Thu]
+Root cause was NOT =derived-mode-p= (that works in both batch and the daemon — my initial guess was wrong). It was the lexical-binding special-var trap: =eww-config.el= is =lexical-binding: t= and the advice =my-eww--inject-user-agent= let-binds url.el's =url-request-extra-headers=, but the file never declared that var special. The byte-compiler bound it lexically, so the injected User-Agent never reached =url-retrieve= and the desktop UA silently dropped in compiled production (eww still worked, just with the default UA). Verified: the byte-compiled advice returned nil before, =t= after. Fix (commit 6131da8e): a top-level =(defvar url-request-extra-headers)= so the compiler treats it as dynamic and the binding propagates. All 3 advice tests pass; live-reloaded. Same class as the json-object-type and the LSP-test special-var traps — a foreign special var let-bound in a lexical file always needs a compile-time defvar/require.
+** DONE [#B] calendar-sync: a declined single occurrence keeps :STATUS: accepted :bug:solo:
+CLOSED: [2026-06-25 Thu]
+A recurring event declined for just one occurrence synced out with =:STATUS: accepted= (chime then faithfully showed it). Root cause (diagnosed by a chime session, 2026-06-24): =calendar-sync--apply-single-exception= merged the override's =:attendees= but never re-derived =:status=, so the occurrence kept the series master's accepted status, and =calendar-sync--filter-declined= (which keys off =:status=) didn't drop it. Fix (TDD): in =apply-single-exception=, when overriding =:attendees=, re-derive =:status= via =calendar-sync--find-user-status= against =calendar-sync-user-emails=. Four new tests in =test-calendar-sync--apply-single-exception.el= (declined → "declined"; no-attendee override → inherited intact; accepted override → accepted; override without the user → inherited intact); recurrence + find-user-status + integration suites unchanged. Live-reloaded; a manual re-sync ran clean. The specific 2026-06-24 Arusyak occurrence is past now (its RECURRENCE-ID override aged out of the feed), so the live confirmation lands on the next single-occurrence decline.
+** DONE [#C] ledger-config is orphaned — ledger-mode never configured :bug:quick:
+CLOSED: [2026-06-28 Sun]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-21
+:END:
+Resolved by wiring (Craig's call 2026-06-28: wire it, not delete). Already landed in commit 6ec857ae (2026-06-24, "feat(ledger): un-orphan ledger-config and rewrite clean-on-save") — =init.el:123= now requires =ledger-config=, after this task's 2026-06-21 review, so the task was stale. Confirmed it loads cleanly; the =cj/executable-find-or-warn "ledger"= guard is in its =:config=. Follow-on audit + guardrail-UX work filed as its own task below.
+** DONE [#C] Remove unused system-power keybindings :refactor:quick:solo:
+CLOSED: [2026-06-28 Sun]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-20
+:END:
+Removed the per-command leaf keys (s/r/e/l/L/E/S) under =C-; != and the prefix map. =C-; != now binds directly to =cj/system-command-menu= (the completing-read menu), via =cj/register-command=, so every command stays reachable through the menu and the leaf keys are freed. Updated the module commentary and the two keymap tests (now assert the direct-command binding + no submap; the menu-dispatch test already covers reachability). 2/2 keymap + 18/18 system-cmd tests green; live-reloaded; confirmed =C-; != resolves to =cj/system-command-menu=. Decision was Craig's (2026-06-28, cj comment): remove them all.
+** CANCELLED [#C] dirvish image previews missing in the pictures dir :bug:
+CLOSED: [2026-06-25 Thu]
+Craig couldn't reproduce — image previews render fine in dirvish now. Cancelled.
+** DONE [#C] ai-term test isolation: collapse-split leaks state breaking display-rule :bug:test:
+CLOSED: [2026-06-25 Thu]
+Root cause found: the display rule's 4th action =cj/--ai-term-display-saved= splits per the globals =cj/--ai-term-last-direction= / =cj/--ai-term-last-size=, captured on the last toggle-off. The collapse-split multi-window and single-window tests call =cj/ai-term= (which captures those globals) but only let-bound =cj/--ai-term-last-was-bury=, so they leaked =last-direction= = below into =display-rule=, which then split below (left-col 0) instead of right (left-col 40). Confirmed by instrumenting: every window/split global was identical fresh vs after-collapse, but the leaked =last-direction= flipped the directional split. Fix: let-bind =cj/--ai-term-last-direction= + =cj/--ai-term-last-size= to nil in both collapse-split tests, isolating the capture-state globals the way the roundtrip test in the same file already does. Full ai-term suite now 158/158 green.
+** DONE [#C] dirvish leaves stray buffers; should be single-instance like org-capture :bug:
+CLOSED: [2026-06-25 Thu]
+Diagnosed and fixed (commit e190648b). The single-instance frame behavior already existed (=cj/dirvish-popup-focus-existing= raises the open popup on a second Super+F; =q= tears it down). Measured the litter: navigating a dirvish session piles up one dired buffer per directory (2 -> 6 over three subdirs), but =dirvish-quit= reaps them all back to baseline. So the leak was only when the popup is closed WITHOUT =q= — closing the Hyprland float or losing focus bypassed =dirvish-quit= and orphaned the session's buffers. Fix: a =delete-frame-functions= hook scoped to the "dirvish" popup frame runs =dirvish-quit= on every close path (verified: navigated session drops back to baseline on frame close without q). Deliberately did NOT enable =dired-kill-when-opening-new-dired-buffer= — it's off on purpose (dirvish-config.el:208) because it breaks mark-in-A-then-move-to-B; the popup-scoped reap leaves regular =C-x d= sessions and that workflow untouched.
+** DONE [#C] ai-term: step between running ai-terms even when detached :feature:solo:next:
+CLOSED: [2026-06-25 Thu]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-22
+:END:
+Implemented 2026-06-25 (commit 79cbccb5): =cj/ai-term-next= now cycles every active agent (a live buffer OR a live tmux session) keyed on the project dir and ordered by buffer name, and stepping onto a detached one attaches it (=cj/--ai-term-show-or-create= recreates the terminal, which reattaches the session). New pure helpers =cj/--ai-term-next-agent-dir= + =cj/--ai-term-active-agent-dirs= with 10 ERT tests; the live-buffer swap path is unchanged. Live check filed under Manual testing and validation.
+** DONE [#C] Compare terminal themeability: EAT vs vterm vs ghostel :feature:solo:next:
+CLOSED: [2026-06-25 Thu]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-22
+:END:
+Researched 2026-06-24. All three expose the 16 ANSI colors as Emacs faces (=eat-term-color-*=, =vterm-color-*=, =ghostel-color-*=, each inheriting =ansi-color-*= / =term-color-*=). Ghostel is the most live-themeable: it alone registers an =enable-theme-functions= resync hook (repaints live buffers on a theme change) and exposes a dedicated =ghostel-default= face for the terminal's default fg/bg. EAT (pure elisp, where the faces are the real render source) and vterm (native, faces read at render) both expose themeable palettes but need a buffer reload to pick up a theme switch and give less default-fg/bg control. Outcome: Craig is moving F12 to EAT anyway, for pure-elisp face control and the fun of theming it — see "F12 pops EAT instead of ghostel" above, which carries the resync tradeoff knowingly.
+** CANCELLED [#D] Un-pin ghostel from 0.33.0 once upstream fixes #422/#423 :bug:
+CLOSED: [2026-06-26 Fri 04:56]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-20
+:END:
+ghostel is held at 0.33.0 (=ghostel-20260604.2049=, commit 5779a2adceb2) in =modules/term-config.el= to dodge the 0.35.x native-PTY crash. When dakra/ghostel ships a fix for #422 (Linux malloc/signal reentrancy) and #423 (macOS recursive lock), restore =:ensure t= (drop the pin comment) and =package-upgrade ghostel=, then re-run the open-ghostel-in-a-GUI-frame survival check. Watch the two issues for the fixing commit.
+
+archsetup automated the zig 0.15.2 pin (managed =install_zig_pin= step, sha-verified, unit-tested). If the un-pinned ghostel bumps its ghostty dependency to a newer zig, send archsetup the new version + sha256 so it bumps its =ZIG_VERSION= / =ZIG_SHA256= constants (=inbox-send archsetup=).
+** DONE [#C] EAT diff green and red too bright :quick:
+CLOSED: [2026-06-28 Sun]
+Darkened the added/removed line backgrounds. Added =eat-term-color-22= (added green) and =eat-term-color-52= (removed red) to the eat section of =scripts/theme-studio/WIP.json= at about half their former brightness — =#005F00= -> =#002f00=, =#5F0000= -> =#2f0000= (Craig: pick an appropriate darkness from WIP.json; halved, symmetric, still clearly green/red). Regenerated =themes/WIP-theme.el= via build-theme.el and re-applied the WIP theme live in the daemon (confirmed: =eat-term-color-22= = #002f00, =-52= = #2f0000). EAT uses each face's :foreground as the palette value for both fg and bg paint, so darkening the foreground darkens the diff background.
+Scope notes: (1) the green index (22) was confirmed via =C-h F=; the red (52) is the symmetric ANSI-256 dark-diff counterpart — if removed lines don't darken, the real index needs a live sample. (2) Only the line backgrounds are themed; the brighter within-line word-highlight shades are different (unconfirmed) indices, left for a live sample if Craig still finds them bright. (3) A 256-cube override is global (hits every terminal program emitting color 22/52). (4) Studio round-trip caveat: a future studio re-export may drop these cube faces until the studio formally tracks them. Visual darkness confirm filed under "Manual testing and validation".
+** DONE [#B] eat semi-char mode swallows zoom-out :bug:solo:
+CLOSED: [2026-06-27 Sat]
+Shipped in commit 69fee81f: =eat-semi-char-mode-map= now binds =C--= -> =text-scale-decrease= and =C-0= -> =cj/eat-text-scale-reset= (=eat-config.el:495-496=). Original report below.
+In =eat-semi-char-mode= (the AI session buffers) =C--= is bound to =eat-self-input= and forwarded to the terminal, so it never reaches =text-scale-decrease= and the font can only grow. On velox 2026-06-27 a session climbed to text-scale 17 (~20x, unreadable) with no in-buffer way down. Fix (binding in =eat-semi-char-mode-map= works for eat, unlike ghostel): =C--= to =text-scale-decrease=, =C-0= to =(text-scale-set 0)=. Tradeoff: =C--= no longer forwarded to the terminal (Claude TUI and tmux do not use it), =C-0= shadows =digit-argument= inside eat buffers only. From the home-emacs inbox handoff 2026-06-27. Roam KB 799e8ab5-1c2c-4874-9abc-dff2ec354181.