aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-15 20:52:42 -0500
committerCraig Jennings <c@cjennings.net>2026-06-15 20:52:42 -0500
commit8292829809eeec545aadd361d9334af14071e307 (patch)
tree62249127bb4c783bca17818bd7ac0902339ef0cc
parent4289d0b97ce3afac3c5bf1402a1b20340fc6be48 (diff)
downloaddotemacs-8292829809eeec545aadd361d9334af14071e307.tar.gz
dotemacs-8292829809eeec545aadd361d9334af14071e307.zip
chore(todo): file studio inbox tasks, archive resolved
-rw-r--r--todo.org376
1 files changed, 183 insertions, 193 deletions
diff --git a/todo.org b/todo.org
index 283d82177..2dab8bac3 100644
--- a/todo.org
+++ b/todo.org
@@ -44,35 +44,12 @@ Tags are additive. For example, a small wrong-behavior fix can be
=:bug:quick:=, and a feature that requires internal restructuring can be
=:feature:refactor:=.
* Emacs Open Work
-** DONE [#A] theme-studio: 2D gallery color picker for assignment dropdowns :feature:studio:
-CLOSED: [2026-06-15 Mon]
-Replaced the per-face color dropdown (mkColorDropdown popup in app.js) with a 2D grid in the palette-panel shape: galleryModel(cur,palette,ground) in app-core.js (pure; reuses columnsFromPalette) returns a default chip, an optional (gone) cell, and rows = ground strip then one row per family (members dark->light, one selected). 5 node tests + #gallerytest browser gate. Trigger and ‹ › step buttons unchanged; applies to all three tiers. From the roam inbox 2026-06-15.
-** TODO [#C] theme-studio: extract a ground() helper for the repeated bg/fg literal :refactor:quick:solo:studio:
-The literal {bg:MAP['bg'],fg:MAP['p']} repeats 21 times across app.js and palette-actions.js, plus more in the browser gates. Extract a ground() helper that returns it and replace every call site. Purely mechanical, no behavior change; the node tests + browser gates are the safety net. From the 2026-06-15 refactor review.
-** DONE [#C] theme-studio: palette display toggle for base colors vs full palette :feature:
-CLOSED: [2026-06-15 Mon]
-Added an arrow control on the palette: right collapses each column to its base color (ground steps collapse to bg/fg too), down shows the full spans. #paltoggletest covers it. Commit 5ab506d9.
-** TODO [#C] theme-studio: raise the max color spans to 5 :feature:studio:
-Increase the palette's maximum span count to 5, for a smoother, slower transition across a color. From the roam inbox.
-*** VERIFY which control caps below 5 — current maxes are all 8
-On review the per-column span control, the ground span control, and regenColumn all already cap at 8 (well above 5), so there's no sub-5 limit to raise. Either 5 is already reachable, or the intended control is a different one (e.g. the generator emits base-only columns — spanCount is hardcoded 0 in palette-generator-ui.js). Need Craig to point at the control he's hitting.
+** TODO [#A] theme-studio: deploy-wip button on the browser page :feature:studio:
+Add a button on the theme-studio page that runs the make deploy-wip target locally (build WIP.json into the theme, live-reload the daemon). The page is served from file://, so the browser can't run make directly. Needs a local bridge: a tiny localhost helper the button POSTs to, or a watched trigger file the page writes. Pick the mechanism before building. From the roam inbox 2026-06-15.
** TODO [#A] theme-studio: cannot reassign fg color :bug:studio:
Selecting the fg tile, changing its value, and clicking update errors that an fg already exists instead of updating it. The update path treats a reassign as an add. From the roam inbox.
-** DONE [#A] theme-studio: flag gone color assignments with a distinctive border :feature:studio:
-CLOSED: [2026-06-15 Mon]
-Swatches whose assigned color resolves to "(gone)" now carry a solid red outline (distinct from the dashed unused-tile flag). #gonetest covers it. Commit 0529189a.
-** DONE [#A] theme-studio: flag unused palette tiles and columns :feature:
-CLOSED: [2026-06-15 Mon]
-usedPaletteHexes reverse-lookup over syntax/ui/package assignments + ground; a tile referenced nowhere gets a dashed outline, an all-unused column gets a dashed box. Biased safe (never flags a used color). Node tests + #unusedtest. Commit 7e7b871f.
** TODO [#A] theme-studio: remove the in-table preview column :refactor:studio:
Drop the per-row preview from the assignment tables and rely on the live buffer preview alone. Requires auditing every assignment view so each face in the faces column has a situationally appropriate representation in the live preview. Craig wants a reusable project workflow for that periodic audit, created before the refit so it can drive the fix. From the roam inbox.
-** TODO [#B] emacs: tag tasks by module name for sorting :refactor:studio:
-Replace topic tagging with single-word module tags: :studio: for everything under scripts/theme-studio/, module-named tags elsewhere, :multi: for cross-area work. Drop bug/enhancement-style tags since work should be chosen on other bases. This changes the current six-tag convention, so update the priority-scheme section to document it, rewrite the task-audit workflow to reconcile tasks against the module scheme, then run the audit. Queue for end of session. From the roam inbox.
-** TODO [#C] theme-studio: compact the contrast column to a half-filled symbol :solo:quick:studio:
-Represent contrast as a colored number colored like the current text. The word passed is unnecessary
-** DONE [#C] theme-studio: equalize style and box button cluster sizing :refactor:quick:studio:
-CLOSED: [2026-06-15 Mon]
-Shrank .sbtn from 26x24 to the 17x15 box-button size (font 13px), so the style and box clusters match and the row returns to roughly its pre-cluster height. Commit 44128931.
** TODO [#A] calendar-sync drops final occurrences, resurrects cancelled meetings :bug:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-13
@@ -124,17 +101,6 @@ Implemented 2026-06-06 in =modules/calibredb-epub-config.el=:
*** TODO Embed Calibre DB metadata into the EPUB files
Surfaced 2026-06-06 while building the bookmark naming: the metadata embedded in the EPUB files' OPF is worse than Calibre's database metadata. nov reads the embedded OPF and got truncated titles ("Frege" vs the filename's "Frege: A Guide for the Perplexed"), author-sort "Last, First" forms ("Christie, Agatha"), and lost punctuation ("A.B.C." -> "A B C"). The filenames (from Calibre's curated DB) are the good copy. Fix on the Calibre side: select all (or by library), run "Edit metadata -> Embed metadata into book files" so the DB metadata is written into each EPUB's OPF. Consider auditing author vs author_sort first. After embedding, the in-file metadata matches the library and any tool reading the files (nov, other readers, re-imports) gets the good data. Not an Emacs task; Calibre-side bulk maintenance.
-** DONE [#A] Face and font diagnostic popup at point :feature:
-CLOSED: [2026-06-15 Mon]
-Read-only popup diagnosing why text at point paints as it does (face stack by source, merged attributes, real font vs declared family, theme/config/inherit provenance). Spec: [[id:98f065cf-8bd5-46a0-ac24-da94d66855ad][face-font-diagnostic-popup-spec-implemented.org]]. Building in modules/face-diagnostic.el: pure core cj/--face-diagnosis-at returns the report plist; cj/describe-face-at-point renders it into a read-only help buffer. From the roam inbox — "do this one first."
-*** 2026-06-15 Mon @ 12:19:41 -0500 Phase 1 — core read model + buffer classifier landed
-modules/face-diagnostic.el: cj/--face-diagnosis-at returns groups 0-2 (buffer classification, character context, face stack by source) via small pure helpers. 17 ERT tests (tests/test-face-diagnostic.el), byte-compile clean. Not yet wired into init.el; the interactive command and keybinding land in Phase 4.
-*** 2026-06-15 Mon @ 12:26:52 -0500 Phase 2 — merged attributes + real font landed
-cj/--face-diag-merged-attributes folds the ordered, remap-expanded spec stack ("computed"); cj/--face-diag-real-font reports font-at or "unavailable" under batch. Settles spec decision #7 (hand-fold, tested on overlay-over-text-prop, default-remap, and face-symbol fixtures). 23 ERT tests total, byte-compile clean.
-*** 2026-06-15 Mon @ 12:30:30 -0500 Phase 3 — provenance trace landed
-cj/--face-diag-provenance returns per-face provenance: themes from theme-face, config from saved/customized-face, the :inherit chain, and the attributes still unspecified that fall to the default. Version-sensitive internals sit behind small tolerant accessors. 30 ERT tests total, byte-compile clean.
-*** 2026-06-15 Mon @ 12:37:16 -0500 Phase 4 — render + popup wiring landed
-cj/describe-face-at-point renders the diagnosis into the read-only *Face Diagnosis* buffer (cj/face-diagnostic-mode), with region-scan mode and an out-of-scope banner; required in init.el; live-verified in the daemon (it already surfaces the auto-dim remaps). Command name settled as cj/describe-face-at-point. Deferred to follow-up: clickable face-name buttons (plain text for now) and the module-header allowlist entry; the keybinding is Craig's to pick.
** DOING [#A] Global yes-or-no-p fset defeats every strong confirmation :bug:quick:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-13
@@ -161,6 +127,10 @@ Fixed 2026-06-13: cmail gets =mu4e-trash-folder= "/cmail/Trash"; refile is a per
:END:
From the 2026-06 config audit (verified against the live daemon). =early-init.el:69= =(setq native-comp-deferred-compilation nil)= — the obsolete alias of =native-comp-jit-compilation= — turns JIT native compilation OFF entirely, not "synchronous" as the comment claims: 19 .eln files exist for 184 packages, ~100 of 121 modules run interpreted for the daemon's lifetime, and system-defaults.el:42-44's speed-3/8-jobs/always-compile settings are dead. Plus =early-init.el:113-116= restores =gc-cons-threshold= to the captured STOCK default (800000, verified) post-startup — frequent small GC pauses forever. Together these plausibly feed the filed org-capture 15-20s task more than anything in the capture path itself. Actions: retest the old "Selecting deleted buffer" race on 30.2 and re-enable JIT (or AOT sweep); set a deliberate 16-64MB threshold (or gcmh). Check both before burning time on the capture-perf debug task.
+** TODO [#B] theme-studio: sort newest colors near the top :feature:studio:
+Newly added colors currently land after the ground layer (bg/fg), low in the order. Surface them near the first entry instead, in both the palette color list and the gallery/dropdown, since the most recently added colors are usually the ones being worked on. From the roam inbox 2026-06-15.
+** TODO [#B] emacs: tag tasks by module name for sorting :refactor:studio:
+Replace topic tagging with single-word module tags: :studio: for everything under scripts/theme-studio/, module-named tags elsewhere, :multi: for cross-area work. Drop bug/enhancement-style tags since work should be chosen on other bases. This changes the current six-tag convention, so update the priority-scheme section to document it, rewrite the task-audit workflow to reconcile tasks against the module scheme, then run the audit. Queue for end of session. From the roam inbox.
** TODO [#B] 2026-06 full config audit — findings backlog :refactor:
Module-by-module review of all 121 modules + init/early-init, holistic passes (startup/perf, stability, UX consistency, package strategy), and spin-offs into pearl, chime, emacs-wttrin. Method: parallel read-only review agents per module group; key claims spot-verified (incl. against the live daemon) before filing. Run 2026-06-11/12, COMPLETE. Tally: ~165 module findings + ~40 holistic + 30 spin-off ≈ 235 total; 40 high-impact bugs filed as standalone tasks above this parent; the rest live in the group children below. Spin-off findings delivered as inbox handoffs to pearl, chime, and emacs-wttrin (2026-06-12-0057). Start with the synthesis child below for the recommended attack order.
@@ -802,22 +772,6 @@ Decided 2026-06-11 (Craig): #0d0b0a is the canonical background — the three dr
*** TODO org-todo color mismatch: test expects #ff2a00, theme renders #a7502d
=dupre-theme-org-todo= (test:130) asserts the org-todo foreground is "#ff2a00" (intense-red), but the theme renders "#a7502d" (red-1). Design call: should org-todo be the bright intense-red or the muted red-1? Fix whichever side loses the decision.
-** DONE [#B] dwim-shell: zip overwrites its own name, backup timestamp never expands, dired menu key dead :bug:quick:solo:
-CLOSED: [2026-06-13 Sat]
-From the 2026-06 config audit, =modules/dwim-shell-config.el=:
-- =:338= — single-file zip is =zip -r '<<fne>>.<<e>>' '<<f>>'= — reconstructs the input filename as the archive ("Zip file structure invalid"; directories produce =foo.=). Should be ='<<fne>>.zip'= like the tar-gzip sibling.
-- =:549= — backup destination single-quotes =$(date ...)= so the substitution is literal: =foo.txt.$(date +%Y%m%d_%H%M%S).bak=. Move it outside the quotes or format-time-string in Elisp.
-- =:932= — dired-mode binding "M-S-d" is unreachable (Meta+Shift+d generates M-D); the dirvish binding two lines down is correctly "M-D". Fix + the stale commentary at dirvish-config.el:30.
-Fixed 2026-06-13: zip single-file template now ='<<fne>>.zip'=; backup uses =format-time-string= in Elisp (real =YYYYMMDD_HHMMSS= stamp, dropped the now-unneeded =date= util); dired key M-S-d→M-D + dirvish-config.el:30 doc corrected. Both command strings extracted into top-level builders (=cj/dwim-shell--zip-single-file-command=, =cj/dwim-shell--dated-backup-command=) so they're unit-testable without the dwim-shell-command package — the command defuns live in its use-package :config, which the batch harness doesn't load. 2 builder tests green in make; live daemon confirms all three (backup stamp, .zip, dired M-D). Real backup/zip run + the dired keypress are a VERIFY.
-
-** DONE [#B] ERC: double mention notifications + tautological server list :bug:quick:solo:
-CLOSED: [2026-06-14 Sun]
-From the 2026-06 config audit, =modules/erc-config.el=:
-- =:281= — =erc-modules= includes the built-in =notifications= module AND :config adds =cj/erc-notify-on-mention= to the same hook — every mention fires two desktop notifications. Pick one path (keep the custom one, slated for messenger unification).
-- =:100= — =cj/erc-connected-servers=: inside =with-current-buffer=, the free =erc-server-process= is the buffer's own local value, so the eq test is tautologically true — returns ALL ERC buffers (channels, dead connections). Use =erc-server-buffer-p= + =erc-server-process-alive=.
-- =:238= — =user-whole-name= read at load but =user-constants= only required at compile time (same trap as auth-config/keyboard-macros).
-Fixed 2026-06-14: removed =notifications= from =erc-modules= (kept the custom =cj/erc-notify-on-mention=, so one notification per mention); rewrote =cj/erc-connected-servers= to filter on =(erc-server-buffer-p)= + =(erc-server-process-alive)= instead of the tautological self-eq; moved =user-constants= to a runtime require. New test-erc-config-connected-servers.el (live-server-only + empty cases) 2 green; module byte-compiles. erc-config not reloaded into the daemon (live IRC session) — takes effect on restart. VERIFY for the one-notification + real-server-list behavior.
-
** TODO [#B] erc-yank silently publishes >5-line pastes as public gists :bug:
=modules/erc-config.el:345= — C-y in any ERC buffer auto-creates a public gist for anything over 5 lines: clipboard content goes to a public URL with no confirmation, and no executable-find guard for =gist= (errors mid-send if absent). Privacy trap. Add a =yes-or-no-p= gate or drop the package for plain C-y. From the 2026-06 config audit.
@@ -1368,14 +1322,6 @@ Update the =dev-fkeys.el= header comment (L33) — TS/JS is no longer punted; th
** TODO [#B] heavy-box comment inserts non-comment lines :bug:solo:
=modules/custom-comments.el:427= — =cj/--comment-heavy-box= interior/empty lines carry no comment prefix, so in line-comment languages (elisp, Python) C-; C h injects syntax-breaking bare =*...= lines. The existing test characterizes the broken output (asserts =^\*.*\*$=). Prefix interiors like =cj/--comment-box= does; add the missing min-length validation (negative width hits make-string with a raw error); fix the test to assert corrected output. From the 2026-06 config audit.
-** DONE [#B] help-config: three defects in one small file :bug:quick:solo:
-CLOSED: [2026-06-13 Sat]
-From the 2026-06 config audit, =modules/help-config.el=:
-- =:67= — =cl-return-from= inside a plain =defun= (no cl-block): declining the save prompt signals "No catch for tag" instead of canceling. =cl-defun= or restructure.
-- =:108= — =:hook (info-mode . info-persist-history-mode)= is dead twice: Info's hook is =Info-mode-hook= (capital I), and =info-persist-history-mode= doesn't exist anywhere. Implement the intent or delete.
-- =:111= — auto-mode-alist maps .info to an interactive command that KILLS the buffer mid find-file — programmatic =find-file-noselect= of any .info destroys buffers and pops Info windows. Drop the entry; keep the explicit command. Zero test coverage on this module (the two broken paths are exactly the untested ones).
-Fixed 2026-06-13: (1) extracted the save/cancel/open decision into a pure =cj/--info-open-plan= and routed =cj/open-with-info-mode= through it — no more =cl-return-from=, declining cancels cleanly; (2) deleted the dead =:hook= and the empty =:preface=; (3) dropped the destructive =auto-mode-alist= .info entry (kept =cj/open-with-info-mode= as an M-x command and =cj/browse-info-files= on C-h i). New test-help-config.el covers the planner (open / save-then-open / cancel) — 3 green; module loads clean. Stale daemon state (the .info auto-mode entry + the bogus info-mode-hook entry) cleared by hand so the running session is correct without a restart. Interactive Info open + find-file-no-longer-destructive are a VERIFY.
-
** TODO [#B] jumper: register collisions and dead-marker errors :bug:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-13
@@ -1394,11 +1340,6 @@ Spec: [[id:540bf06b-16b8-46c6-b459-c40d1b9c795d][keybinding-console-safety-spec-
** TODO [#B] ledger-config is orphaned — ledger-mode never configured :bug:quick:
Nothing requires =modules/ledger-config.el= (verified by grep), so .dat/.ledger/.journal open without ledger-mode, reports, or flycheck-ledger. The module looks finished, not staged (unlike duet-config, which documents its pre-alpha orphaning). Decide: wire into init.el (+ =cj/executable-find-or-warn= for the ledger binary) or delete. From the 2026-06 config audit.
-** DONE [#B] markdown live preview clobbered by markdown-mode :bug:quick:solo:
-CLOSED: [2026-06-13 Sat]
-=modules/markdown-config.el:54= defines bare =markdown-preview=, which markdown-mode redefines the moment the first .md loads — the impatient-mode live preview is dead and F2 silently runs the package command (agent verified in the live daemon). Also =:61= guards on =(boundp 'httpd-process)=, a variable that doesn't exist in simple-httpd — use =(httpd-running-p)=. And the =:config= =(setq imp-set-user-filter 'markdown-html)= at line 41 is doubly dead (function-not-variable, symbol names nothing) — delete. Rename to =cj/markdown-preview=, rebind F2. From the 2026-06 config audit.
-Fixed 2026-06-13: renamed the defun to =cj/markdown-preview= and rebound =<f2>=; guard is now =(httpd-running-p)=; deleted the dead =(setq imp-set-user-filter 'markdown-html)= (impatient-mode use-package is now =:defer t= only). Added a guard test (server-down → user-error); 4/4 green; live daemon confirms =cj/markdown-preview= defined and guarding. Browser-render check is a VERIFY.
-
** TODO [#B] Messenger window/key unification :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-13
@@ -1465,11 +1406,6 @@ Folded the external review via spec-response. Craig accepted D1-D5; baked them p
*** 2026-06-04 Thu @ 23:30:18 -0500 External re-review: ready
Re-reviewed [[id:b54c94a0-d762-4b41-afd7-cf5593ce6675][docs/specs/vterm-to-ghostel-migration-spec-implemented.org]] after incorporation. Verdict: =Ready=. No further blocking review notes; implementation can start from the phase plan and acceptance criteria in the spec.
-** DONE [#B] modeline runs synchronous git on the redisplay path, unguarded :bug:solo:
-CLOSED: [2026-06-14 Sun]
-=modules/modeline-config.el:173,154,145= — the mode-line :eval calls vc-backend/vc-state/vc-working-revision (synchronous git) on TTL expiry; a slow or unmounted filesystem stalls ALL redisplay. The cache key computes =file-truename= on every render (the "one stat per refresh" comment is wrong), and nothing is condition-case-wrapped, so a signal lands inside the mode-line eval. Defer the truename behind the TTL check; wrap the fetch in condition-case caching nil. From the 2026-06 config audit.
-Fixed 2026-06-14 (Approach A): dropped =file-truename= from =cj/modeline-vc-cache-key= (key is now =(file show-remote)=, no per-render stat; a moved symlink is caught at the next TTL refresh when vc-backend resolves the link fresh). Wrapped =cj/modeline-vc-fetch= in =condition-case ... (error nil)= so a git signal on a slow/unmounted FS degrades to no-VC-info instead of breaking redisplay. Rewrote the two truename cache-key tests to assert the cheap key; added a fetch-swallows-vc-errors test. 9 modeline vc tests green; live daemon confirms the 2-element key and nil-on-error fetch.
-
** DOING [#B] Module-by-module hardening
:PROPERTIES:
:LAST_REVIEWED: 2026-06-05
@@ -2809,9 +2745,6 @@ configuration (=text-config=, =diff-config=, =ledger-config=,
=games-config=, =mu4e-org-contacts-setup=, =telega-config=,
=httpd-config=, =org-agenda-config-debug=).
-** DONE [#B] org-faces: custom header-row face layer + theme-studio app :feature:theme-studio:spec:
-CLOSED: [2026-06-15 Mon]
-Named, theme-agnostic faces for org TODO keywords and priorities, wired via org-todo-keyword-faces / org-priority-faces, plus a dedicated theme-studio "org-faces" app beside elfeed and mu4e. Spec: [[id:35578114-8c29-43af-97a2-fdfea01a802e][org-faces-spec-implemented.org]]. All four decisions resolved (prefix org-faces-, real defface defaults, auto-dim repointed to org-faces-*-dim, all 10 keywords). Phase 1 (modules/org-faces-config.el + 5 ERT tests), Phase 2 (auto-dim-config.el repoint), Phase 3 (theme-studio org-faces app) all landed and verified; the build-theme round-trip is confirmed mechanically. Residual visual check is a VERIFY under Manual testing and validation.
** TODO [#B] org-roam :config triggers the 15-20s refile scan synchronously at first idle :bug:solo:
=modules/org-roam-config.el:78-79= — org-roam is =:defer 1=, so its :config calls =cj/build-org-refile-targets= at 1s idle, BEFORE the 5s background timer (=org-refile-config.el:144-151=); on a cold cache the 30k-file scan runs inline and freezes Emacs at first idle. Drop the call — org-roam is loaded long before the 5s timer fires. Likely a player in the filed org-capture 15-20s perf task (=[#B] Optimize org-capture target building performance=) — check both together. From the 2026-06 config audit.
@@ -2837,14 +2770,6 @@ From the roam inbox: the page-me workflow should always identify which project e
** TODO [#B] Scratch buffer background a shade lighter than default :feature:
Make *scratch* just-noticeably lighter than the normal background so it reads as the scratch buffer. Simplest is a buffer-local face remap on *scratch*; Craig is fine routing it through org-faces if a theme hook is wanted. The exact lightening delta is an aesthetic call to tune visually. From the roam inbox.
-** DONE [#B] slack-config lifecycle gaps :bug:quick:solo:
-CLOSED: [2026-06-14 Sun]
-From the 2026-06 config audit, =modules/slack-config.el=:
-- =:265= — w / @ / # bound to commands neither autoloaded nor in :commands — void-function before slack loads. Add to :commands.
-- =:246= — =cj/slack-close-all-buffers= reads =slack-current-buffer= (declared but unbound) without the boundp guard its sibling has — void-variable on C-; S Q before slack loads.
-- =:259= — raw =global-set-key= for C-; S bypasses =cj/register-prefix-map= (signal/erc use it); invisible to the keybindings registry and the planned unification enumeration.
-Fixed 2026-06-14: added =slack-message-write-another-buffer=, =slack-message-embed-mention=, =slack-message-embed-channel= to =:commands= (w/@/# now autoload); guarded =cj/slack-close-all-buffers= with =buffer-local-boundp= (no void-variable on C-; S Q before slack loads); switched =global-set-key= to =(cj/register-prefix-map "S" cj/slack-keymap "slack")= (+require keybindings). New test-slack-config-close-all.el green; module loads, C-; S registered in the registry. Not reloaded into the live daemon (active slack session) — restart to apply. VERIFY for the pre-load key safety.
-
** TODO [#B] Split window opens the dashboard in the other window :feature:quick:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-10
@@ -2857,42 +2782,10 @@ When splitting with C-x 2 (=split-window-below=) or C-x 3 (=split-window-right=)
** TODO [#B] system-defaults: top-level server-start unguarded in batch :bug:quick:solo:
=modules/system-defaults.el:140= — raw module load under =--batch= (make validate-modules on a machine with no daemon socket) starts a server from a batch process; the suite only passes because the testutil stubs it. Wrap in =(unless noninteractive ...)= — the repo's established guard for this defect class; same guard stops the =custom-file= =make-temp-file= at line 104 littering temp files per batch load. From the 2026-06 config audit.
-** DONE [#B] theme-studio color columns :feature:studio:
-CLOSED: [2026-06-13 Sat]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-11
-:END:
-Show the palette as hue-grouped strips (dark→light) over the existing flat, individually-editable palette. Grouping is by OKLCH hue from the hex, so renaming a color never moves it. A per-strip count control generates a symmetric ramp (N → base ±N) from the strip's most-saturated color; regenerate is authoritative, repointing surviving-step references by lightness rank and leaving removed-step references a visible "(gone)". The ground strip is synthesized from the bg/fg assignments and pinned first; the standalone ramp panel is removed. Designed in [[file:docs/theme-studio-color-families-spec.org][docs/theme-studio-color-families-spec.org]]. Codex-reviewed Ready 2026-06-10 after response folded: pivoted from name-derived families to hex-derived families over a flat palette, which designs out the name-grammar/import-inference and chip-ownership blockers. All review findings dispositioned; both open decisions resolved. Builds on and supersedes the palette-ramps v1 ramp UI.
-
-All six phases landed 2026-06-10 (commits ebe18d51, 74db9a52, 111687b0, e7ae18c4, 77783126, f6ab0001, 9daeff15, and the Phase 6 commit); =make theme-studio-test= green (98 node tests, 16 browser gates). Code-complete and self-verified. The hue-adjacent warm-color grouping limitation is filed as a separate research task (=~/color-sorting.org=). Remaining: the manual aesthetic/fidelity sign-off under the Manual testing parent (hue grouping reads right, regenerate-replace reads as deliberate, removed-step "(gone)" is clear). Mark this DONE once that passes.
-Retired 2026-06-13: the current implementation no longer uses color-derived hue families. Palette entries carry stable structural column ids, generated colors stay in their originating column, renames do not move tiles, and =#columntest= / =#roundtriptest= pin the behavior.
-*** 2026-06-10 Wed @ 01:17:45 -0500 Family model core landed
-Phase 1 (commit =ebe18d51=, grouping reworked in =77783126=). =familiesFromPalette=, =regenFamily=, =rankByLightness=, =stepRepointPlan= in app-core.js, pure and hex-derived. Grouping started as gap-clustering + flat neutral threshold; after the design discussion it became nearest-hue-anchor bucketing (no single-linkage chaining) + a lightness-scaled neutral threshold (pale tints keep their hue, mid grays go neutral). regenFamily handles n=0 without ramp()'s clamp; stepRepointPlan maps survivors / lists removed by signed lightness rank. 20 node tests including the green/yellow split and the no-chaining case. Open: hue-adjacent warm colors still merge — research task above (=~/color-sorting.org=).
-*** 2026-06-10 Wed @ 01:17:45 -0500 Family sort core landed
-Phase 2 (commit =74db9a52=). =sortFamilies=/=sortFamilyMembers=: neutrals first, then chromatic by base hue (rounded so a hue hair doesn't outrank lightness), ties by base lightness then hex; members dark→light. Display-only; stored palette order untouched. 4 node tests.
-*** 2026-06-10 Wed @ 01:17:45 -0500 Family-strip rendering landed
-Phase 3 (commit =111687b0=, columns =e7ae18c4=). renderPalette restructured into the pinned ground strip + hue-sorted family columns (top→bottom dark→light), chips keep per-chip rename/remove/select, move-arrows/drag dropped. #familytest gate locks the structure + rename-stays-in-strip. Existing palette flows stay green.
-*** 2026-06-10 Wed @ 01:17:45 -0500 Count control + regenerate landed
-Phase 4 (commit =f6ab0001=). Per-chromatic-strip count input (0-4); setting N regenerates the family as base ±N, repointing survivor references by lightness rank and leaving removed-step references on their now-gone hex. Also fixed the neutral-threshold curve to taper at both lightness ends (symmetric Munsell) so chroma-eased dark/light extremes keep their hue. #counttest gate covers count up/down + the survivor/removed reference behavior.
-*** 2026-06-10 Wed @ 01:17:45 -0500 Base edit + retire ramp panel landed
-Phase 5 (commit =9daeff15=). Editing a family base recolors the whole family (shared =regenFamilyInPlace= with the count control); editing a ground swatch writes the bg/fg assignment. The standalone ramp panel (button, panel, JS, CSS, #ramptest) is removed — fan a color via its column's count instead. #baseedittest gate covers base-edit recolor + reference follow + the bg-swatch edit.
-*** 2026-06-10 Wed @ 01:17:45 -0500 Warnings, seeding, export, README close-out landed
-Phase 6 (commit =c175e2be=). Export stays a flat palette and import needs no reconstruction (#roundtriptest: export→import→export byte-identical). =seedPkgmap= reads the flat palette unchanged. The too-similar warning stays on the full palette — the planned ramp-step exemption was dropped after analysis: ramp steps are a stepL apart (well above the ΔE threshold) so they never warn, and exempting same-family pairs would hide genuine near-duplicates (caught by #deltatest). README documents families, the ground strip, the count control/regenerate, removed-step references, and the ramp-panel removal.
-
** TODO [#B] theme-studio: dashboard preview icons missing, list items unthemed :bug:studio:
Found while theme-testing the live dashboard against the preview.
- The navigator icons don't render in the preview at all, showing as mojibake. The nerd-font glyphs have no font fallback in the browser.
- No way to set the color of the project, bookmark, and recent-files list items. The preview renders those entries as plain unstyled text, and the dashboard app exposes no editable face for them.
-** DONE [#B] theme-studio delete entire color column :feature:quick:solo:studio:
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-13
-:END:
-Add a column-level delete affordance to the palette. Deleting a normal color column removes the base color and every generated/imported tile in that column from the screen and from =PALETTE=. Ground remains pinned and non-deletable. Existing assignments that referenced removed tiles should follow the current deleted-color behavior: they remain on the old hex and appear as recoverable =(gone)= values rather than silently repointing.
-
-Acceptance notes: add a browser gate proving a column delete removes all entries with that stable =columnId=, leaves other columns alone, leaves ground non-deletable, and preserves any references to deleted hexes as gone values.
-
-Shipped 2026-06-13 in commit =2cf730d5=: normal column headers have a delete button, ground has no delete affordance, deleted names feed =lastGone= for recovery, and =#columntest= pins the behavior.
-
** TODO [#B] theme-studio guide-support features :feature:studio:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-13
@@ -2925,44 +2818,8 @@ Spec draft: [[id:2df157b8-c7c1-47a9-b080-d9586c6f424c][theme-studio-palette-gene
Build a constraint-first palette generator for Theme Studio: start from bg/fg, generate editable palette columns in OKLCH, preview candidate columns before applying them, and apply proposals by append/replace/regenerate modes while preserving stable column ids and existing assignments where possible. V1 is palette-only, not automatic face assignment; generator output becomes normal editable columns after apply.
-** DONE [#B] theme-studio palette ramps + contrast safety v1 :feature:studio:
-CLOSED: [2026-06-13 Sat]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-10
-:END:
-The v1 build from [[file:docs/theme-studio-palette-ramps-spec.org][theme-studio-palette-ramps-spec.org]] (Ready, Codex-reviewed). Two coupled features: a ramp generator (one base color → harmonized tonal ramp) and background-contrast safety (worst-case floor over a face's foreground set + safe-lightness guidance).
-
-All five phases + the README close-out landed 2026-06-09 (commits 1d51a332, 9da6c663, e7021bfe, 1d8b9f9e, 843bbf08, 23926837); =make theme-studio-test= green (78 node tests, 12 browser gates). Code-complete and self-verified. Remaining: the aesthetic and real-Emacs-fidelity sign-off under the Manual testing parent below (does a ramp harmonize, does the safe band read clearly, does a "safe" tint actually read behind real syntax). Mark this DONE once that passes.
-Retired 2026-06-13: this parent is no longer active planning work. The useful pieces are already in the current tool, and later structural-column work replaced the standalone ramp workflow.
-*** 2026-06-09 Tue @ 18:40:20 -0500 Ramp generator core landed
-Phase 1 (commit =1d51a332=). =ramp(baseHex, {n, stepL, chromaEase})= in app-core.js → ={steps: [{hex, clamped, offset}], adjusted}= or ={steps: [], error: 'bad-hex'}=. Holds the OKLCH hue, steps lightness by =stepL=, quadratic chroma-ease toward the extremes, gamut-clamps each step; knobs clamp to range with the clamped knob named in =adjusted= (n=2/stepL=0.08/chromaEase=0.5 defaults). 10 node tests (mid/near-white/near-black bases, hue-hold, chroma ease, knob clamping, malformed hex), suite 55→65, =make theme-studio-test= green. The app-core integrity stripper now drops =import= lines too.
-*** 2026-06-09 Tue @ 19:06:46 -0500 Ramp UI in palette landed
-Phase 2 (commit =9da6c663=). A "ramp" button opens a panel that generates from the current color and previews the steps (named per source swatch, clamp badge on out-of-gamut steps); the n/stepL/chroma-ease controls default to 2/0.08/0.5. Click a step or "add all" to insert adjacent to the source in -n..+n order; name collisions skip (no overwrite), hex duplicates add with a flag. New #ramptest gate pins count, ordered insertion, collision skip, and the clamp badge. Verified headless + screenshot.
-*** 2026-06-09 Tue @ 18:53:16 -0500 Foreground-set + floor + L_max core landed
-Phase 3 (commit =e7021bfe=). =fgSetFor=, =floor=, =lMax= and the =COVERED_FACES= constant in app-core.js, all pure and explicit-state. fgSetFor returns {set:[{hex,label}]} or a structured reason ('out-of-scope'/'empty'); floor returns {ratio, limitingHex, limitingLabel}; lMax scans L from black to bracket the dark-side crossing then binary-searches it (tol 0.001), status ok/none/all/clamp. 13 node tests including the keyword-blue #67809c fixture and lMax's none/all/clamp branches. Suite 65→78, =make theme-studio-test= green.
-*** 2026-06-09 Tue @ 19:06:46 -0500 Worst-case contrast readout landed
-Phase 4 (commit =1d8b9f9e=). The five covered overlay faces show the worst-case floor over their foreground set (live syntax colors + default fg) and name the limiting foreground; a syntax-color edit repaints them. Out-of-scope faces keep the single-pair cell; an empty set reads "no fg set". Verdict is WCAG AA by default. New #contrasttest gate pins the readout, the keyword-blue limiting case, the single-pair fallback, and the no-set string.
-*** 2026-06-09 Tue @ 19:06:46 -0500 Safe-lightness picker guidance landed
-Phase 5 (commit =843bbf08=). The OKLCH picker gets a "safe for" selector over the covered faces; the C×L plane shades the lightness band too light to keep that face readable, with the L_max ceiling (via =lMax= at the current chroma) as the band's lower edge. A too-dark foreground shades the whole plane. New #safetest gate pins band-shows-for-covered-face and hides-when-none. Verified headless + screenshot.
-*** 2026-06-09 Tue @ 19:06:46 -0500 README + test-surface close-out landed
-Commit =23926837=. README documents the ramp controls and defaults, the worst-case floor / limiting foreground, the five covered faces, the safe-lightness guidance, and WCAG-drives-PASS-FAIL with APCA as a diagnostic; the browser-gate list is updated. =make theme-studio-test= carries all new node tests and the #ramptest/#contrasttest/#safetest gates. All acceptance criteria met.
-
** TODO [#B] theme-studio: preview element locate (hover + click) :feature:theme-studio:spec: :spec:studio:
General preview interaction: hover any element for its section / face / value, click a current-pane element to flash and jump to its assignment row, off-pane elements hover-only (not clickable). Built on a face -> owning-app registry that previews also read for cross-pane live rendering. Spec: [[id:fbcf0e20-1328-42b4-aa36-3401509e7816][theme-studio-preview-locate-spec.org]]. Prerequisite for the richer org-agenda preview.
-** DONE [#B] theme-studio preview face mislinks (org, erc, flycheck) :bug:quick:solo:studio:
-CLOSED: [2026-06-13 Sat]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-11
-:END:
-Found by Craig 2026-06-11 during the manual-test walk (org case), then a full audit of all 20 bespoke previews confirmed three mislinks; the rest are clean:
-
-1. app.js:564 (org) — "Heading three" carries data-face org-headline-todo on a line with no TODO keyword; org-headline-todo's docstring says it applies to the part of the headline after the TODO keyword. Fix: add an org-todo keyword span mirroring the DONE line at app.js:563 (stars = org-level-3, keyword = org-todo, text = org-headline-todo).
-2. app.js:765-766 (erc) — swapped: craig's own message text is erc-default-face and bob's is erc-input-face. erc-input-face is "ERC face used for your input"; swap them.
-3. app.js:720 (flycheck) — swapped: brackets carry flycheck-delimited-error and the content flycheck-error-delimiter. In flycheck's delimiters highlighting style the delimiter strings get error-delimiter and the enclosed text gets delimited-error; swap them.
-
-Pin with a browser-gate assertion that these preview elements link the right faces (e.g. the org headline-todo span sits after an org-todo span; the erc my-message line uses input-face).
-Fixed 2026-06-13: org heading three now has an =org-todo= keyword before =org-headline-todo=, flycheck delimiters/content are mapped to =flycheck-error-delimiter= / =flycheck-delimited-error= correctly, and ERC own/remote message text use =erc-input-face= / =erc-default-face=. Added =#previewlinktest= to pin all three mappings.
-
** TODO [#B] theme-studio save button does not overwrite the current theme file :bug:studio:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-13
@@ -2998,14 +2855,6 @@ Tentative follow-up from walking through the generator algorithms. Consider spli
This may be cancelled if the extra distinction makes the generator harder to understand. Before implementing, decide final names and whether ground-aware source should include only bg/fg or also ground span steps.
-** DONE [#B] theme-studio spans should stop at bg and fg bounds :bug:quick:solo:studio:
-CLOSED: [2026-06-15 Mon]
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-13
-:END:
-Done in commit 25e2d2ad: regenColumn ramps the dark side toward bg and the light side toward fg, bounded by the ground endpoints; pure black/white duplicates still skipped. Node tests + the #counttest bounds assertion pin it.
-From the roam inbox: spanning a color should not generate colors beyond the current ground endpoints. No generated color should be darker than =bg= or lighter than =fg=. If the darker side hits =#000000=, do not create duplicate black tiles; if the lighter side hits =#ffffff=, do not create duplicate white tiles. Apply this to normal column spans and ground spans as appropriate, then pin with Node tests for the ramp/column plan and a browser gate for the palette UI.
-
** TODO [#B] theme-studio UI face inheritance needs a spec :feature:studio:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-13
@@ -3035,11 +2884,16 @@ While in here, audit individual leaf chords for other non-TTY keys (any =C-RET=,
** TODO [#B] Unified popup placement and dismissal rules :feature:
All transient popups should follow one set of principles. Placement: when the Emacs frame is wider than tall, the popup rises from the right; when square or taller, from the bottom — settle the aspect-ratio threshold and the pop-out percentage. Dismissal: C-c C-c when there's an accept action, C-c C-k when there's a cancel, otherwise =q= closes the window. This generalizes two existing tasks — ai-term adaptive placement (the aspect-ratio docking) and the messenger window/key unification spec (the C-c C-c / C-c C-k dismissal) — into one config-wide policy. From the roam inbox.
-** DONE [#B] vertico-prescient clobbers orderless filtering :bug:quick:solo:
-CLOSED: [2026-06-13 Sat]
-=modules/selection-framework.el:250= — =vertico-prescient-mode= defaults =vertico-prescient-enable-filtering t=, overriding =completion-styles= to prescient inside vertico sessions; the orderless config at :151 is dead exactly where it matters. Set =vertico-prescient-enable-filtering nil= — orderless matches, prescient sorts (and this resolves the dead =vertico-sort-function= finding in the buffer/window-libs child the other way around). From the 2026-06 config audit.
-Fixed 2026-06-13: added =:custom (vertico-prescient-enable-filtering nil)= to the vertico-prescient use-package. Live daemon confirmed filtering nil + =completion-styles (orderless basic)= with the mode re-enabled — orderless matches, prescient sorts. No ERT test (framework defcustom, unexercisable in the stubbed-use-package harness); the in-session matching check is a VERIFY under Manual testing and validation.
-
+** TODO [#C] theme-studio: drop the too-similar-colors message below the palette :refactor:studio:
+Remove the too-similar-colors warning under the palette display. It isn't useful there; the same information is reachable per-assignment through the inline contrast field. From the roam inbox 2026-06-15.
+** TODO [#C] theme-studio: extract a ground() helper for the repeated bg/fg literal :refactor:quick:solo:studio:
+The literal {bg:MAP['bg'],fg:MAP['p']} repeats 21 times across app.js and palette-actions.js, plus more in the browser gates. Extract a ground() helper that returns it and replace every call site. Purely mechanical, no behavior change; the node tests + browser gates are the safety net. From the 2026-06-15 refactor review.
+** TODO [#C] theme-studio: raise the max color spans to 5 :feature:studio:
+Increase the palette's maximum span count to 5, for a smoother, slower transition across a color. From the roam inbox.
+*** VERIFY which control caps below 5 — current maxes are all 8
+On review the per-column span control, the ground span control, and regenColumn all already cap at 8 (well above 5), so there's no sub-5 limit to raise. Either 5 is already reachable, or the intended control is a different one (e.g. the generator emits base-only columns — spanCount is hardcoded 0 in palette-generator-ui.js). Need Craig to point at the control he's hitting.
+** TODO [#C] theme-studio: compact the contrast column to a half-filled symbol :solo:quick:studio:
+Represent contrast as a colored number colored like the current text. The word passed is unnecessary
** TODO [#C] archsetup Waybar Wi-Fi module should show no-internet state :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-13
@@ -4416,11 +4270,6 @@ These may override useful defaults - review and pick better bindings:
:END:
Display slack.el message and thread buffers in a dedicated popup window (side or bottom) and reuse that one window instead of spawning a new window per buffer. Likely a =display-buffer-alist= rule (or popper integration) in =modules/slack-config.el=.
-** DONE [#C] Swap buffer delete/diff keys — destructive on capital :refactor:quick:solo:
-CLOSED: [2026-06-13 Sat]
-=modules/custom-buffer-file.el:515= binds =d= = =cj/delete-buffer-and-file= and =D= = =cj/diff-buffer-with-file=. Destructive commands should be the capital, and diff is the one hit often (when saving a buffer changed on disk). Swap them: =D= = delete, =d= = diff. From the roam inbox.
-Swapped 2026-06-13: =cj/buffer-and-file-map= now binds =d= = =cj/diff-buffer-with-file=, =D= = =cj/delete-buffer-and-file=. keymap-lookup test added; live daemon re-bound by hand (defvar-keymap won't reassign a bound var on reload). Keypress check is a VERIFY under Manual testing and validation.
-
** TODO [#C] Terminal GPG pinentry Completion :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-05
@@ -4485,30 +4334,8 @@ Craig, 2026-06-11 manual-test walk: the color picker's background is hard to dis
:END:
From the roam inbox: the =show= button for the raw JSON export does not fit the main theme-design workflow, but it may still be useful for debugging. Decide whether to hide it behind a debugging affordance, rename it, or remove it. Quick UI cleanup once the desired debugging surface is chosen; not marked solo because it is a workflow preference call.
-** DONE [#C] theme-studio: remove redundant reset button on package faces :refactor:quick:studio:
-CLOSED: [2026-06-15 Mon]
-Removed the per-row reset column (package faces was the only tier with one). It was not equivalent to the bulk reset next to "lock all": per-row reset one face, bulk resets every unlocked face. Single reset path now, matching syntax/ui tiers. Commit 7a7b1c16.
-** DONE [#C] theme-studio: rename preview sample names :feature:quick:solo:studio:
-CLOSED: [2026-06-15 Mon]
-Renamed the preview personas Alice→Christine and Eve→Evan across the mu4e/signel/telega previews, with the mu4e header spacing adjusted to stay aligned. Commit 44128931.
** TODO [#C] theme-studio: restrict the cursor row to its background :bug:studio:
The UI table gives the cursor face the full control set (fg, B/I/U/S, box), but Emacs only honors the cursor face's :background. Its shape is cursor-type, not a face attribute, so every other control on that row is a no-op once the theme loads. Restrict the cursor row to just its background swatch so the studio doesn't present controls Emacs drops.
-** DONE [#C] theme-studio Rust + Zig language previews :feature:studio:
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-11
-:END:
-Requested by Craig 2026-06-11: add Rust and Zig code samples to the language previews (samples.py currently carries Elisp, Go, Python, TypeScript, Java, C, C++, Shell). Each sample should exercise the treesit token categories distinctive to its language (Rust: lifetimes, macros, attributes, traits; Zig: comptime, builtins, error unions), then regenerate theme-studio.html and extend the test surface.
-
-Shipped 2026-06-13: Rust and Zig were added to =samples.py= and =generate.py=, with generator tests pinning the language-specific category coverage.
-
-** DONE [#C] theme-studio separate tile selection from name editing :feature:quick:solo:studio:
-:PROPERTIES:
-:LAST_REVIEWED: 2026-06-13
-:END:
-From the roam inbox: clicking a palette tile should have predictable intent. Single-click anywhere on a tile should select the whole color tile for editing/assignment. Double-clicking the name should enter name-edit mode with the cursor at the beginning of the name. Add browser-gate coverage for both paths so accidental single-click name edits do not regress.
-
-Shipped 2026-06-13: tile names are read-only until double-clicked; single-click selects the tile, and =#columntest= pins single-click vs double-click behavior.
-
** TODO [#C] theme-studio terminal/ANSI colors :feature:studio:
theme-studio represents GUI faces only; terminal colors aren't surfaced at all. Scope decided 2026-06-09: GUI-first faces, NOT full per-face display-class fallback. Two pieces:
@@ -4573,10 +4400,6 @@ Findings from the 2026-05-20 investigation:
navigation commands.
- Live experiment scratch file: =~/dashboard-overscroll-experiment.el=.
-** CANCELLED [#D] Desktop quick-capture: Note + Recipe types :feature:solo:
-CLOSED: [2026-06-15 Mon]
-Superseded 2026-06-15: the desktop popup was simplified to a single Task into the org-roam inbox (no Bug/Event, no template menu), so adding Note/Recipe types to the popup subset no longer applies.
-
** Emacs Packages — Curl-Friendly Web Service Wrappers
Ideas for new Emacs packages following the same pattern as wttrin: HTTP GET to a simple web service, render results in a buffer, optionally show summary in the mode-line. All of these share the async fetch + caching infrastructure already proven in wttrin.
Captured On: [2026-04-04 Sat]
@@ -8411,3 +8234,170 @@ CLOSED: [2026-06-12 Fri]
Relocated from the global capture inbox 2026-06-06. When inside a projectile project, C-c c t (Task) files into that project's root todo.org under the "<Project> Open Work" header. If the project has no todo.org, fall back to the global inbox-file and warn naming the project.
Implemented 2026-06-06 in =modules/org-capture-config.el=: a shared project-aware =function= capture target (=cj/--org-capture-project-location=) used by =C-c c t= (Task, =* TODO=) and a new =C-c c b= (Bug, =* TODO [#C]=). Matches an existing top-level "... Open Work" heading (so ~/.emacs.d hits "Emacs Open Work") and creates "<Capitalized project> Open Work" only when absent. Outside a project / no todo.org -> global inbox under "Inbox" (with a warning in the no-todo.org case). 15 ERT tests in =tests/test-org-capture-config-project-target.el=; daemon e2e confirmed a real capture lands "** TODO [#C] ..." prepended under Open Work. Manual verify filed under the Manual testing and validation parent. NOTE: the matching "<Project> Resolved Work" header for the wrap-up workflow is a separate concern, not handled here.
+** DONE [#A] theme-studio: 2D gallery color picker for assignment dropdowns :feature:studio:
+CLOSED: [2026-06-15 Mon]
+Replaced the per-face color dropdown (mkColorDropdown popup in app.js) with a 2D grid in the palette-panel shape: galleryModel(cur,palette,ground) in app-core.js (pure; reuses columnsFromPalette) returns a default chip, an optional (gone) cell, and rows = ground strip then one row per family (members dark->light, one selected). 5 node tests + #gallerytest browser gate. Trigger and ‹ › step buttons unchanged; applies to all three tiers. From the roam inbox 2026-06-15.
+** DONE [#C] theme-studio: palette display toggle for base colors vs full palette :feature:
+CLOSED: [2026-06-15 Mon]
+Added an arrow control on the palette: right collapses each column to its base color (ground steps collapse to bg/fg too), down shows the full spans. #paltoggletest covers it. Commit 5ab506d9.
+** DONE [#A] theme-studio: flag gone color assignments with a distinctive border :feature:studio:
+CLOSED: [2026-06-15 Mon]
+Swatches whose assigned color resolves to "(gone)" now carry a solid red outline (distinct from the dashed unused-tile flag). #gonetest covers it. Commit 0529189a.
+** DONE [#A] theme-studio: flag unused palette tiles and columns :feature:
+CLOSED: [2026-06-15 Mon]
+usedPaletteHexes reverse-lookup over syntax/ui/package assignments + ground; a tile referenced nowhere gets a dashed outline, an all-unused column gets a dashed box. Biased safe (never flags a used color). Node tests + #unusedtest. Commit 7e7b871f.
+** DONE [#C] theme-studio: equalize style and box button cluster sizing :refactor:quick:studio:
+CLOSED: [2026-06-15 Mon]
+Shrank .sbtn from 26x24 to the 17x15 box-button size (font 13px), so the style and box clusters match and the row returns to roughly its pre-cluster height. Commit 44128931.
+** DONE [#A] Face and font diagnostic popup at point :feature:
+CLOSED: [2026-06-15 Mon]
+Read-only popup diagnosing why text at point paints as it does (face stack by source, merged attributes, real font vs declared family, theme/config/inherit provenance). Spec: [[id:98f065cf-8bd5-46a0-ac24-da94d66855ad][face-font-diagnostic-popup-spec-implemented.org]]. Building in modules/face-diagnostic.el: pure core cj/--face-diagnosis-at returns the report plist; cj/describe-face-at-point renders it into a read-only help buffer. From the roam inbox — "do this one first."
+*** 2026-06-15 Mon @ 12:19:41 -0500 Phase 1 — core read model + buffer classifier landed
+modules/face-diagnostic.el: cj/--face-diagnosis-at returns groups 0-2 (buffer classification, character context, face stack by source) via small pure helpers. 17 ERT tests (tests/test-face-diagnostic.el), byte-compile clean. Not yet wired into init.el; the interactive command and keybinding land in Phase 4.
+*** 2026-06-15 Mon @ 12:26:52 -0500 Phase 2 — merged attributes + real font landed
+cj/--face-diag-merged-attributes folds the ordered, remap-expanded spec stack ("computed"); cj/--face-diag-real-font reports font-at or "unavailable" under batch. Settles spec decision #7 (hand-fold, tested on overlay-over-text-prop, default-remap, and face-symbol fixtures). 23 ERT tests total, byte-compile clean.
+*** 2026-06-15 Mon @ 12:30:30 -0500 Phase 3 — provenance trace landed
+cj/--face-diag-provenance returns per-face provenance: themes from theme-face, config from saved/customized-face, the :inherit chain, and the attributes still unspecified that fall to the default. Version-sensitive internals sit behind small tolerant accessors. 30 ERT tests total, byte-compile clean.
+*** 2026-06-15 Mon @ 12:37:16 -0500 Phase 4 — render + popup wiring landed
+cj/describe-face-at-point renders the diagnosis into the read-only *Face Diagnosis* buffer (cj/face-diagnostic-mode), with region-scan mode and an out-of-scope banner; required in init.el; live-verified in the daemon (it already surfaces the auto-dim remaps). Command name settled as cj/describe-face-at-point. Deferred to follow-up: clickable face-name buttons (plain text for now) and the module-header allowlist entry; the keybinding is Craig's to pick.
+** DONE [#B] dwim-shell: zip overwrites its own name, backup timestamp never expands, dired menu key dead :bug:quick:solo:
+CLOSED: [2026-06-13 Sat]
+From the 2026-06 config audit, =modules/dwim-shell-config.el=:
+- =:338= — single-file zip is =zip -r '<<fne>>.<<e>>' '<<f>>'= — reconstructs the input filename as the archive ("Zip file structure invalid"; directories produce =foo.=). Should be ='<<fne>>.zip'= like the tar-gzip sibling.
+- =:549= — backup destination single-quotes =$(date ...)= so the substitution is literal: =foo.txt.$(date +%Y%m%d_%H%M%S).bak=. Move it outside the quotes or format-time-string in Elisp.
+- =:932= — dired-mode binding "M-S-d" is unreachable (Meta+Shift+d generates M-D); the dirvish binding two lines down is correctly "M-D". Fix + the stale commentary at dirvish-config.el:30.
+Fixed 2026-06-13: zip single-file template now ='<<fne>>.zip'=; backup uses =format-time-string= in Elisp (real =YYYYMMDD_HHMMSS= stamp, dropped the now-unneeded =date= util); dired key M-S-d→M-D + dirvish-config.el:30 doc corrected. Both command strings extracted into top-level builders (=cj/dwim-shell--zip-single-file-command=, =cj/dwim-shell--dated-backup-command=) so they're unit-testable without the dwim-shell-command package — the command defuns live in its use-package :config, which the batch harness doesn't load. 2 builder tests green in make; live daemon confirms all three (backup stamp, .zip, dired M-D). Real backup/zip run + the dired keypress are a VERIFY.
+** DONE [#B] ERC: double mention notifications + tautological server list :bug:quick:solo:
+CLOSED: [2026-06-14 Sun]
+From the 2026-06 config audit, =modules/erc-config.el=:
+- =:281= — =erc-modules= includes the built-in =notifications= module AND :config adds =cj/erc-notify-on-mention= to the same hook — every mention fires two desktop notifications. Pick one path (keep the custom one, slated for messenger unification).
+- =:100= — =cj/erc-connected-servers=: inside =with-current-buffer=, the free =erc-server-process= is the buffer's own local value, so the eq test is tautologically true — returns ALL ERC buffers (channels, dead connections). Use =erc-server-buffer-p= + =erc-server-process-alive=.
+- =:238= — =user-whole-name= read at load but =user-constants= only required at compile time (same trap as auth-config/keyboard-macros).
+Fixed 2026-06-14: removed =notifications= from =erc-modules= (kept the custom =cj/erc-notify-on-mention=, so one notification per mention); rewrote =cj/erc-connected-servers= to filter on =(erc-server-buffer-p)= + =(erc-server-process-alive)= instead of the tautological self-eq; moved =user-constants= to a runtime require. New test-erc-config-connected-servers.el (live-server-only + empty cases) 2 green; module byte-compiles. erc-config not reloaded into the daemon (live IRC session) — takes effect on restart. VERIFY for the one-notification + real-server-list behavior.
+** DONE [#B] help-config: three defects in one small file :bug:quick:solo:
+CLOSED: [2026-06-13 Sat]
+From the 2026-06 config audit, =modules/help-config.el=:
+- =:67= — =cl-return-from= inside a plain =defun= (no cl-block): declining the save prompt signals "No catch for tag" instead of canceling. =cl-defun= or restructure.
+- =:108= — =:hook (info-mode . info-persist-history-mode)= is dead twice: Info's hook is =Info-mode-hook= (capital I), and =info-persist-history-mode= doesn't exist anywhere. Implement the intent or delete.
+- =:111= — auto-mode-alist maps .info to an interactive command that KILLS the buffer mid find-file — programmatic =find-file-noselect= of any .info destroys buffers and pops Info windows. Drop the entry; keep the explicit command. Zero test coverage on this module (the two broken paths are exactly the untested ones).
+Fixed 2026-06-13: (1) extracted the save/cancel/open decision into a pure =cj/--info-open-plan= and routed =cj/open-with-info-mode= through it — no more =cl-return-from=, declining cancels cleanly; (2) deleted the dead =:hook= and the empty =:preface=; (3) dropped the destructive =auto-mode-alist= .info entry (kept =cj/open-with-info-mode= as an M-x command and =cj/browse-info-files= on C-h i). New test-help-config.el covers the planner (open / save-then-open / cancel) — 3 green; module loads clean. Stale daemon state (the .info auto-mode entry + the bogus info-mode-hook entry) cleared by hand so the running session is correct without a restart. Interactive Info open + find-file-no-longer-destructive are a VERIFY.
+** DONE [#B] markdown live preview clobbered by markdown-mode :bug:quick:solo:
+CLOSED: [2026-06-13 Sat]
+=modules/markdown-config.el:54= defines bare =markdown-preview=, which markdown-mode redefines the moment the first .md loads — the impatient-mode live preview is dead and F2 silently runs the package command (agent verified in the live daemon). Also =:61= guards on =(boundp 'httpd-process)=, a variable that doesn't exist in simple-httpd — use =(httpd-running-p)=. And the =:config= =(setq imp-set-user-filter 'markdown-html)= at line 41 is doubly dead (function-not-variable, symbol names nothing) — delete. Rename to =cj/markdown-preview=, rebind F2. From the 2026-06 config audit.
+Fixed 2026-06-13: renamed the defun to =cj/markdown-preview= and rebound =<f2>=; guard is now =(httpd-running-p)=; deleted the dead =(setq imp-set-user-filter 'markdown-html)= (impatient-mode use-package is now =:defer t= only). Added a guard test (server-down → user-error); 4/4 green; live daemon confirms =cj/markdown-preview= defined and guarding. Browser-render check is a VERIFY.
+** DONE [#B] modeline runs synchronous git on the redisplay path, unguarded :bug:solo:
+CLOSED: [2026-06-14 Sun]
+=modules/modeline-config.el:173,154,145= — the mode-line :eval calls vc-backend/vc-state/vc-working-revision (synchronous git) on TTL expiry; a slow or unmounted filesystem stalls ALL redisplay. The cache key computes =file-truename= on every render (the "one stat per refresh" comment is wrong), and nothing is condition-case-wrapped, so a signal lands inside the mode-line eval. Defer the truename behind the TTL check; wrap the fetch in condition-case caching nil. From the 2026-06 config audit.
+Fixed 2026-06-14 (Approach A): dropped =file-truename= from =cj/modeline-vc-cache-key= (key is now =(file show-remote)=, no per-render stat; a moved symlink is caught at the next TTL refresh when vc-backend resolves the link fresh). Wrapped =cj/modeline-vc-fetch= in =condition-case ... (error nil)= so a git signal on a slow/unmounted FS degrades to no-VC-info instead of breaking redisplay. Rewrote the two truename cache-key tests to assert the cheap key; added a fetch-swallows-vc-errors test. 9 modeline vc tests green; live daemon confirms the 2-element key and nil-on-error fetch.
+** DONE [#B] org-faces: custom header-row face layer + theme-studio app :feature:theme-studio:spec:
+CLOSED: [2026-06-15 Mon]
+Named, theme-agnostic faces for org TODO keywords and priorities, wired via org-todo-keyword-faces / org-priority-faces, plus a dedicated theme-studio "org-faces" app beside elfeed and mu4e. Spec: [[id:35578114-8c29-43af-97a2-fdfea01a802e][org-faces-spec-implemented.org]]. All four decisions resolved (prefix org-faces-, real defface defaults, auto-dim repointed to org-faces-*-dim, all 10 keywords). Phase 1 (modules/org-faces-config.el + 5 ERT tests), Phase 2 (auto-dim-config.el repoint), Phase 3 (theme-studio org-faces app) all landed and verified; the build-theme round-trip is confirmed mechanically. Residual visual check is a VERIFY under Manual testing and validation.
+** DONE [#B] slack-config lifecycle gaps :bug:quick:solo:
+CLOSED: [2026-06-14 Sun]
+From the 2026-06 config audit, =modules/slack-config.el=:
+- =:265= — w / @ / # bound to commands neither autoloaded nor in :commands — void-function before slack loads. Add to :commands.
+- =:246= — =cj/slack-close-all-buffers= reads =slack-current-buffer= (declared but unbound) without the boundp guard its sibling has — void-variable on C-; S Q before slack loads.
+- =:259= — raw =global-set-key= for C-; S bypasses =cj/register-prefix-map= (signal/erc use it); invisible to the keybindings registry and the planned unification enumeration.
+Fixed 2026-06-14: added =slack-message-write-another-buffer=, =slack-message-embed-mention=, =slack-message-embed-channel= to =:commands= (w/@/# now autoload); guarded =cj/slack-close-all-buffers= with =buffer-local-boundp= (no void-variable on C-; S Q before slack loads); switched =global-set-key= to =(cj/register-prefix-map "S" cj/slack-keymap "slack")= (+require keybindings). New test-slack-config-close-all.el green; module loads, C-; S registered in the registry. Not reloaded into the live daemon (active slack session) — restart to apply. VERIFY for the pre-load key safety.
+** DONE [#B] theme-studio color columns :feature:studio:
+CLOSED: [2026-06-13 Sat]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-11
+:END:
+Show the palette as hue-grouped strips (dark→light) over the existing flat, individually-editable palette. Grouping is by OKLCH hue from the hex, so renaming a color never moves it. A per-strip count control generates a symmetric ramp (N → base ±N) from the strip's most-saturated color; regenerate is authoritative, repointing surviving-step references by lightness rank and leaving removed-step references a visible "(gone)". The ground strip is synthesized from the bg/fg assignments and pinned first; the standalone ramp panel is removed. Designed in [[file:docs/theme-studio-color-families-spec.org][docs/theme-studio-color-families-spec.org]]. Codex-reviewed Ready 2026-06-10 after response folded: pivoted from name-derived families to hex-derived families over a flat palette, which designs out the name-grammar/import-inference and chip-ownership blockers. All review findings dispositioned; both open decisions resolved. Builds on and supersedes the palette-ramps v1 ramp UI.
+
+All six phases landed 2026-06-10 (commits ebe18d51, 74db9a52, 111687b0, e7ae18c4, 77783126, f6ab0001, 9daeff15, and the Phase 6 commit); =make theme-studio-test= green (98 node tests, 16 browser gates). Code-complete and self-verified. The hue-adjacent warm-color grouping limitation is filed as a separate research task (=~/color-sorting.org=). Remaining: the manual aesthetic/fidelity sign-off under the Manual testing parent (hue grouping reads right, regenerate-replace reads as deliberate, removed-step "(gone)" is clear). Mark this DONE once that passes.
+Retired 2026-06-13: the current implementation no longer uses color-derived hue families. Palette entries carry stable structural column ids, generated colors stay in their originating column, renames do not move tiles, and =#columntest= / =#roundtriptest= pin the behavior.
+*** 2026-06-10 Wed @ 01:17:45 -0500 Family model core landed
+Phase 1 (commit =ebe18d51=, grouping reworked in =77783126=). =familiesFromPalette=, =regenFamily=, =rankByLightness=, =stepRepointPlan= in app-core.js, pure and hex-derived. Grouping started as gap-clustering + flat neutral threshold; after the design discussion it became nearest-hue-anchor bucketing (no single-linkage chaining) + a lightness-scaled neutral threshold (pale tints keep their hue, mid grays go neutral). regenFamily handles n=0 without ramp()'s clamp; stepRepointPlan maps survivors / lists removed by signed lightness rank. 20 node tests including the green/yellow split and the no-chaining case. Open: hue-adjacent warm colors still merge — research task above (=~/color-sorting.org=).
+*** 2026-06-10 Wed @ 01:17:45 -0500 Family sort core landed
+Phase 2 (commit =74db9a52=). =sortFamilies=/=sortFamilyMembers=: neutrals first, then chromatic by base hue (rounded so a hue hair doesn't outrank lightness), ties by base lightness then hex; members dark→light. Display-only; stored palette order untouched. 4 node tests.
+*** 2026-06-10 Wed @ 01:17:45 -0500 Family-strip rendering landed
+Phase 3 (commit =111687b0=, columns =e7ae18c4=). renderPalette restructured into the pinned ground strip + hue-sorted family columns (top→bottom dark→light), chips keep per-chip rename/remove/select, move-arrows/drag dropped. #familytest gate locks the structure + rename-stays-in-strip. Existing palette flows stay green.
+*** 2026-06-10 Wed @ 01:17:45 -0500 Count control + regenerate landed
+Phase 4 (commit =f6ab0001=). Per-chromatic-strip count input (0-4); setting N regenerates the family as base ±N, repointing survivor references by lightness rank and leaving removed-step references on their now-gone hex. Also fixed the neutral-threshold curve to taper at both lightness ends (symmetric Munsell) so chroma-eased dark/light extremes keep their hue. #counttest gate covers count up/down + the survivor/removed reference behavior.
+*** 2026-06-10 Wed @ 01:17:45 -0500 Base edit + retire ramp panel landed
+Phase 5 (commit =9daeff15=). Editing a family base recolors the whole family (shared =regenFamilyInPlace= with the count control); editing a ground swatch writes the bg/fg assignment. The standalone ramp panel (button, panel, JS, CSS, #ramptest) is removed — fan a color via its column's count instead. #baseedittest gate covers base-edit recolor + reference follow + the bg-swatch edit.
+*** 2026-06-10 Wed @ 01:17:45 -0500 Warnings, seeding, export, README close-out landed
+Phase 6 (commit =c175e2be=). Export stays a flat palette and import needs no reconstruction (#roundtriptest: export→import→export byte-identical). =seedPkgmap= reads the flat palette unchanged. The too-similar warning stays on the full palette — the planned ramp-step exemption was dropped after analysis: ramp steps are a stepL apart (well above the ΔE threshold) so they never warn, and exempting same-family pairs would hide genuine near-duplicates (caught by #deltatest). README documents families, the ground strip, the count control/regenerate, removed-step references, and the ramp-panel removal.
+** DONE [#B] theme-studio delete entire color column :feature:quick:solo:studio:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-13
+:END:
+Add a column-level delete affordance to the palette. Deleting a normal color column removes the base color and every generated/imported tile in that column from the screen and from =PALETTE=. Ground remains pinned and non-deletable. Existing assignments that referenced removed tiles should follow the current deleted-color behavior: they remain on the old hex and appear as recoverable =(gone)= values rather than silently repointing.
+
+Acceptance notes: add a browser gate proving a column delete removes all entries with that stable =columnId=, leaves other columns alone, leaves ground non-deletable, and preserves any references to deleted hexes as gone values.
+
+Shipped 2026-06-13 in commit =2cf730d5=: normal column headers have a delete button, ground has no delete affordance, deleted names feed =lastGone= for recovery, and =#columntest= pins the behavior.
+** DONE [#B] theme-studio palette ramps + contrast safety v1 :feature:studio:
+CLOSED: [2026-06-13 Sat]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-10
+:END:
+The v1 build from [[file:docs/theme-studio-palette-ramps-spec.org][theme-studio-palette-ramps-spec.org]] (Ready, Codex-reviewed). Two coupled features: a ramp generator (one base color → harmonized tonal ramp) and background-contrast safety (worst-case floor over a face's foreground set + safe-lightness guidance).
+
+All five phases + the README close-out landed 2026-06-09 (commits 1d51a332, 9da6c663, e7021bfe, 1d8b9f9e, 843bbf08, 23926837); =make theme-studio-test= green (78 node tests, 12 browser gates). Code-complete and self-verified. Remaining: the aesthetic and real-Emacs-fidelity sign-off under the Manual testing parent below (does a ramp harmonize, does the safe band read clearly, does a "safe" tint actually read behind real syntax). Mark this DONE once that passes.
+Retired 2026-06-13: this parent is no longer active planning work. The useful pieces are already in the current tool, and later structural-column work replaced the standalone ramp workflow.
+*** 2026-06-09 Tue @ 18:40:20 -0500 Ramp generator core landed
+Phase 1 (commit =1d51a332=). =ramp(baseHex, {n, stepL, chromaEase})= in app-core.js → ={steps: [{hex, clamped, offset}], adjusted}= or ={steps: [], error: 'bad-hex'}=. Holds the OKLCH hue, steps lightness by =stepL=, quadratic chroma-ease toward the extremes, gamut-clamps each step; knobs clamp to range with the clamped knob named in =adjusted= (n=2/stepL=0.08/chromaEase=0.5 defaults). 10 node tests (mid/near-white/near-black bases, hue-hold, chroma ease, knob clamping, malformed hex), suite 55→65, =make theme-studio-test= green. The app-core integrity stripper now drops =import= lines too.
+*** 2026-06-09 Tue @ 19:06:46 -0500 Ramp UI in palette landed
+Phase 2 (commit =9da6c663=). A "ramp" button opens a panel that generates from the current color and previews the steps (named per source swatch, clamp badge on out-of-gamut steps); the n/stepL/chroma-ease controls default to 2/0.08/0.5. Click a step or "add all" to insert adjacent to the source in -n..+n order; name collisions skip (no overwrite), hex duplicates add with a flag. New #ramptest gate pins count, ordered insertion, collision skip, and the clamp badge. Verified headless + screenshot.
+*** 2026-06-09 Tue @ 18:53:16 -0500 Foreground-set + floor + L_max core landed
+Phase 3 (commit =e7021bfe=). =fgSetFor=, =floor=, =lMax= and the =COVERED_FACES= constant in app-core.js, all pure and explicit-state. fgSetFor returns {set:[{hex,label}]} or a structured reason ('out-of-scope'/'empty'); floor returns {ratio, limitingHex, limitingLabel}; lMax scans L from black to bracket the dark-side crossing then binary-searches it (tol 0.001), status ok/none/all/clamp. 13 node tests including the keyword-blue #67809c fixture and lMax's none/all/clamp branches. Suite 65→78, =make theme-studio-test= green.
+*** 2026-06-09 Tue @ 19:06:46 -0500 Worst-case contrast readout landed
+Phase 4 (commit =1d8b9f9e=). The five covered overlay faces show the worst-case floor over their foreground set (live syntax colors + default fg) and name the limiting foreground; a syntax-color edit repaints them. Out-of-scope faces keep the single-pair cell; an empty set reads "no fg set". Verdict is WCAG AA by default. New #contrasttest gate pins the readout, the keyword-blue limiting case, the single-pair fallback, and the no-set string.
+*** 2026-06-09 Tue @ 19:06:46 -0500 Safe-lightness picker guidance landed
+Phase 5 (commit =843bbf08=). The OKLCH picker gets a "safe for" selector over the covered faces; the C×L plane shades the lightness band too light to keep that face readable, with the L_max ceiling (via =lMax= at the current chroma) as the band's lower edge. A too-dark foreground shades the whole plane. New #safetest gate pins band-shows-for-covered-face and hides-when-none. Verified headless + screenshot.
+*** 2026-06-09 Tue @ 19:06:46 -0500 README + test-surface close-out landed
+Commit =23926837=. README documents the ramp controls and defaults, the worst-case floor / limiting foreground, the five covered faces, the safe-lightness guidance, and WCAG-drives-PASS-FAIL with APCA as a diagnostic; the browser-gate list is updated. =make theme-studio-test= carries all new node tests and the #ramptest/#contrasttest/#safetest gates. All acceptance criteria met.
+** DONE [#B] theme-studio preview face mislinks (org, erc, flycheck) :bug:quick:solo:studio:
+CLOSED: [2026-06-13 Sat]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-11
+:END:
+Found by Craig 2026-06-11 during the manual-test walk (org case), then a full audit of all 20 bespoke previews confirmed three mislinks; the rest are clean:
+
+1. app.js:564 (org) — "Heading three" carries data-face org-headline-todo on a line with no TODO keyword; org-headline-todo's docstring says it applies to the part of the headline after the TODO keyword. Fix: add an org-todo keyword span mirroring the DONE line at app.js:563 (stars = org-level-3, keyword = org-todo, text = org-headline-todo).
+2. app.js:765-766 (erc) — swapped: craig's own message text is erc-default-face and bob's is erc-input-face. erc-input-face is "ERC face used for your input"; swap them.
+3. app.js:720 (flycheck) — swapped: brackets carry flycheck-delimited-error and the content flycheck-error-delimiter. In flycheck's delimiters highlighting style the delimiter strings get error-delimiter and the enclosed text gets delimited-error; swap them.
+
+Pin with a browser-gate assertion that these preview elements link the right faces (e.g. the org headline-todo span sits after an org-todo span; the erc my-message line uses input-face).
+Fixed 2026-06-13: org heading three now has an =org-todo= keyword before =org-headline-todo=, flycheck delimiters/content are mapped to =flycheck-error-delimiter= / =flycheck-delimited-error= correctly, and ERC own/remote message text use =erc-input-face= / =erc-default-face=. Added =#previewlinktest= to pin all three mappings.
+** DONE [#B] theme-studio spans should stop at bg and fg bounds :bug:quick:solo:studio:
+CLOSED: [2026-06-15 Mon]
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-13
+:END:
+Done in commit 25e2d2ad: regenColumn ramps the dark side toward bg and the light side toward fg, bounded by the ground endpoints; pure black/white duplicates still skipped. Node tests + the #counttest bounds assertion pin it.
+From the roam inbox: spanning a color should not generate colors beyond the current ground endpoints. No generated color should be darker than =bg= or lighter than =fg=. If the darker side hits =#000000=, do not create duplicate black tiles; if the lighter side hits =#ffffff=, do not create duplicate white tiles. Apply this to normal column spans and ground spans as appropriate, then pin with Node tests for the ramp/column plan and a browser gate for the palette UI.
+** DONE [#B] vertico-prescient clobbers orderless filtering :bug:quick:solo:
+CLOSED: [2026-06-13 Sat]
+=modules/selection-framework.el:250= — =vertico-prescient-mode= defaults =vertico-prescient-enable-filtering t=, overriding =completion-styles= to prescient inside vertico sessions; the orderless config at :151 is dead exactly where it matters. Set =vertico-prescient-enable-filtering nil= — orderless matches, prescient sorts (and this resolves the dead =vertico-sort-function= finding in the buffer/window-libs child the other way around). From the 2026-06 config audit.
+Fixed 2026-06-13: added =:custom (vertico-prescient-enable-filtering nil)= to the vertico-prescient use-package. Live daemon confirmed filtering nil + =completion-styles (orderless basic)= with the mode re-enabled — orderless matches, prescient sorts. No ERT test (framework defcustom, unexercisable in the stubbed-use-package harness); the in-session matching check is a VERIFY under Manual testing and validation.
+** DONE [#C] Swap buffer delete/diff keys — destructive on capital :refactor:quick:solo:
+CLOSED: [2026-06-13 Sat]
+=modules/custom-buffer-file.el:515= binds =d= = =cj/delete-buffer-and-file= and =D= = =cj/diff-buffer-with-file=. Destructive commands should be the capital, and diff is the one hit often (when saving a buffer changed on disk). Swap them: =D= = delete, =d= = diff. From the roam inbox.
+Swapped 2026-06-13: =cj/buffer-and-file-map= now binds =d= = =cj/diff-buffer-with-file=, =D= = =cj/delete-buffer-and-file=. keymap-lookup test added; live daemon re-bound by hand (defvar-keymap won't reassign a bound var on reload). Keypress check is a VERIFY under Manual testing and validation.
+** DONE [#C] theme-studio: remove redundant reset button on package faces :refactor:quick:studio:
+CLOSED: [2026-06-15 Mon]
+Removed the per-row reset column (package faces was the only tier with one). It was not equivalent to the bulk reset next to "lock all": per-row reset one face, bulk resets every unlocked face. Single reset path now, matching syntax/ui tiers. Commit 7a7b1c16.
+** DONE [#C] theme-studio: rename preview sample names :feature:quick:solo:studio:
+CLOSED: [2026-06-15 Mon]
+Renamed the preview personas Alice→Christine and Eve→Evan across the mu4e/signel/telega previews, with the mu4e header spacing adjusted to stay aligned. Commit 44128931.
+** DONE [#C] theme-studio Rust + Zig language previews :feature:studio:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-11
+:END:
+Requested by Craig 2026-06-11: add Rust and Zig code samples to the language previews (samples.py currently carries Elisp, Go, Python, TypeScript, Java, C, C++, Shell). Each sample should exercise the treesit token categories distinctive to its language (Rust: lifetimes, macros, attributes, traits; Zig: comptime, builtins, error unions), then regenerate theme-studio.html and extend the test surface.
+
+Shipped 2026-06-13: Rust and Zig were added to =samples.py= and =generate.py=, with generator tests pinning the language-specific category coverage.
+** DONE [#C] theme-studio separate tile selection from name editing :feature:quick:solo:studio:
+:PROPERTIES:
+:LAST_REVIEWED: 2026-06-13
+:END:
+From the roam inbox: clicking a palette tile should have predictable intent. Single-click anywhere on a tile should select the whole color tile for editing/assignment. Double-clicking the name should enter name-edit mode with the cursor at the beginning of the name. Add browser-gate coverage for both paths so accidental single-click name edits do not regress.
+
+Shipped 2026-06-13: tile names are read-only until double-clicked; single-click selects the tile, and =#columntest= pins single-click vs double-click behavior.
+** CANCELLED [#D] Desktop quick-capture: Note + Recipe types :feature:solo:
+CLOSED: [2026-06-15 Mon]
+Superseded 2026-06-15: the desktop popup was simplified to a single Task into the org-roam inbox (no Bug/Event, no template menu), so adding Note/Recipe types to the popup subset no longer applies.