aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--todo.org270
1 files changed, 135 insertions, 135 deletions
diff --git a/todo.org b/todo.org
index ffc96eb7..c598582f 100644
--- a/todo.org
+++ b/todo.org
@@ -25,23 +25,26 @@ For =PROJECT= headings, use the highest priority of the meaningful child work
inside the project. If a project only contains exploration or review, assign the
priority by the expected decision value rather than the number of files touched.
-Use tags to describe the work shape:
+Use tags to describe the work shape. These six are the ONLY tags allowed on a
+task. Do not invent topic, scope, or status tags — the heading and the parent
+section already carry that context.
- =:bug:= means the current behavior is wrong or likely broken.
- =:feature:= means the task adds a new user-visible capability or workflow.
- =:refactor:= means the task changes structure/ownership without primarily
changing behavior.
+- =:test:= means the task primarily adds or fixes test coverage.
- =:quick:= means the task appears low effort and localized. It is a planning
hint, not a promise; remove it if the task grows during implementation.
- =:solo:= means Claude can do the task end to end with no input from Craig:
bounded scope, no design or preference call, and verifiable in the local
setup (tests, byte-compile, launch). Tasks needing a policy/preference
- decision, visual judgment, or a live remote do not get =:solo:=.
+ decision or a live remote do not get =:solo:=.
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
-** TODO [#A] Calibre Open Work :calibre:
+** TODO [#A] Calibre Open Work
:PROPERTIES:
:LAST_REVIEWED: 2026-06-06
:END:
@@ -64,7 +67,7 @@ Existing bookmarks: the 3 nov bookmarks in =~/sync/org/emacs_bookmarks= were ren
Awaiting Craig's manual confirm: make a NEW bookmark (open an EPUB, hit m) and check the default name is "Author, Title" from the filename.
-*** DOING [#A] Reconsider Calibre keybindings :feature:ux:
+*** DOING [#A] Reconsider Calibre keybindings :feature:
Relocated from the global capture inbox 2026-06-06. Want a discoverable set of keybindings (visible in which-key) for the most frequent calibredb workflows:
- Switch to a library (e.g. Literature), sort by last name, scroll the list.
- Scope/filter the list in place, keeping the current library scope:
@@ -80,13 +83,13 @@ Implemented 2026-06-06 in =modules/calibredb-epub-config.el=:
- Bottom-30% description dock: =calibredb-show-entry-switch= -> =pop-to-buffer= + a =display-buffer-alist= rule for =*calibredb-entry*= (display-buffer-at-bottom, height 0.3); =cj/calibredb-describe-at-point= shows the entry without switching focus so q dismisses it. Same pattern as the signel chat dock.
1 ERT test (the describe command; the transient/bindings/dock need the elpa transient + live calibredb, verified in the daemon). Author "begins-with" is covered well enough by g a's completing-read over "Last, First"; a true regex filter was not built. Awaiting Craig's manual verify (M-B -> ? menu; d/v docked description; H full menu).
-*** TODO Embed Calibre DB metadata into the EPUB files :data:maintenance:
+*** 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.
-** TODO [#A] Lock screen silently fails — slock is X11-only :bug:
+** TODO [#A] Lock screen silently fails — slock is X11-only :bug:quick:
=modules/system-commands.el:105= binds the lockscreen command to =slock=, which can't grab a Wayland session; =cj/system-cmd= launches it detached with output silenced, so C-; ! l does nothing and the screen never locks. Security issue: Craig believes the screen locks when it doesn't. Fix: =hyprlock= (or =swaylock=), ideally resolved per session type via =env-wayland-p= so an X11 fallback survives for other machines. From the 2026-06 config audit.
-** TODO [#A] mu4e: cmail can't trash, no account can refile :bug:
+** TODO [#A] mu4e: cmail can't trash, no account can refile :bug:quick:solo:
=modules/mail-config.el:217-220= — the cmail context (primary account) sets only drafts/sent, so D falls back to default "/trash" which doesn't exist under ~/.mail (=/cmail/Trash= does); and NO context sets =mu4e-refile-folder=, so r targets nonexistent "/archive" everywhere. Accepting mu4e's offer to create the maildir strands mail in a directory mbsync never syncs — messages silently vanish from the server's view. Add =mu4e-trash-folder= to cmail + per-context =mu4e-refile-folder=. From the 2026-06 config audit.
** TODO [#A] calendar-sync drops final occurrences and resurrects cancelled meetings :bug:solo:
@@ -101,7 +104,7 @@ From the 2026-06 config audit (verified against the live daemon). =early-init.el
** TODO [#A] Global yes-or-no-p fset defeats every strong confirmation :bug:quick:
=modules/system-defaults.el:203= =(fset 'yes-or-no-p 'y-or-n-p)= — verified live. Several modules deliberately chose yes-or-no-p as the strong tier for irreversible actions: shutdown/reboot (=system-commands.el:74=, whose comment explicitly says "so a stray RET/space can't trigger them"), "permanently destroy files" (=dwim-shell-config.el:804=), file overwrites (=custom-buffer-file.el:159,199=, =music-config.el:374=). The fset makes all of them single-keystroke — the two-tier design is dead. Drop the fset, or provide a real =cj/confirm-strong= (typed "yes") for the irreversible set. From the 2026-06 config audit.
-** TODO [#B] theme-studio preview face mislinks (org, erc, flycheck) :bug:theme-studio:quick:solo:
+** TODO [#B] theme-studio preview face mislinks (org, erc, flycheck) :bug:quick:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-11
:END:
@@ -113,13 +116,13 @@ Found by Craig 2026-06-11 during the manual-test walk (org case), then a full au
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).
-** TODO [#B] Split window opens the dashboard in the other window :feature:ux:windows:quick:solo:
+** TODO [#B] Split window opens the dashboard in the other window :feature:quick:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-10
:END:
When splitting with C-x 2 (=split-window-below=) or C-x 3 (=split-window-right=), the new/other window should default to the =*dashboard*= buffer instead of mirroring the current buffer. Advise =split-window-below= / =split-window-right= (or rebind the keys) to select the dashboard in the freshly-created window. Keep point in the original window.
-** TODO [#B] theme-studio palette ramps + contrast safety v1 :feature:theme-studio:
+** TODO [#B] theme-studio palette ramps + contrast safety v1 :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-10
:END:
@@ -139,7 +142,7 @@ Phase 5 (commit =843bbf08=). The OKLCH picker gets a "safe for" selector over th
*** 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 color families :feature:theme-studio:
+** TODO [#B] theme-studio color families :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-11
:END:
@@ -159,7 +162,7 @@ Phase 5 (commit =9daeff15=). Editing a family base recolors the whole family (sh
*** 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] Dupre diff-changed / diff-refine-changed legibility :bug:dupre:
+** TODO [#B] Dupre diff-changed / diff-refine-changed legibility :bug:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-11
:END:
@@ -172,7 +175,7 @@ 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]].
-** TODO [#B] dupre-theme test failures :bug:dupre:tests:quick:solo:
+** TODO [#B] dupre-theme test failures :bug:test:quick:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-11
:END:
@@ -185,26 +188,26 @@ 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.
-** TODO [#B] theme-studio guide-support features :feature:theme-studio:
+** TODO [#B] theme-studio guide-support features :feature:
From the color-assignment guide work (2026-06-08): make the tool support the guide without mandating it — everything a seed, an advisory, or a view, never a gate. Two specs to write, both deriving from the rewritten guide and its seed table ([[file:scripts/theme-studio/theme-coloring-guide.org][theme-coloring-guide.org]]).
*** 2026-06-08 Mon @ 19:08:00 -0500 Seeding-engine spec written and Ready
[[file:docs/design/theme-studio-seeding-engine-spec.org][theme-studio-seeding-engine-spec.org]] — role table + face→role maps for syntax/UI/org, OKLCH shade generation, reseed dupre-revised to the compact mapping. Codex-reviewed, Ready. Implementation tracked under the seeding-engine parent below.
-*** TODO Guide-support views and advisories spec :solo:
+*** TODO Guide-support views and advisories spec
Five optional surfaces, all dismissible and non-blocking, in one collapsible panel where they advise: (1) CVD-simulation toggle on previews (deuteranopia/protanopia/tritanopia); (2) squint/blur preview toggle; (3) lightness-ramp view + palette advisories (accent count over 6-8, roles separated only by red/green) — depends on the OKLCH/ΔE core; (4) definition-vs-call / weight advisories; (5) state-over-syntax preview (region/search/diff tint over real syntax-colored text). Sequence: rewritten guide reviewed → seeding-engine spec → this. Advisories (3, 4) layer on the perceptual-metrics feature.
-** TODO [#B] theme-studio seeding engine :feature:theme:theme-studio:
+** TODO [#B] theme-studio seeding engine :feature:
Spec (Ready): [[file:docs/design/theme-studio-seeding-engine-spec.org][spec]]. Role table → guide-correct defaults for syntax/UI/org; reseed dupre-revised.json to the compact mapping; opens seeded with an all-tier reseed button. Depends on the perceptual-metrics colormath.js core for OKLCH shade generation, so it runs after that feature's Phase 1.
*** TODO Seed model + seed() + #seedtest :solo:
Phase 1. Palette anchors + OKLCH shade generation (reusing colormath.js), the ROLES table, and the three face→role maps as data; pure seed(). Gate: #seedtest asserts representative syntax/UI/org faces resolve correctly (bi→blue-grey, fnd→gold+bold, region bg-only, link underlined, org-level-1 strongest, org-code literal lane) and a non-org bespoke package (magit) keeps its curated seed.
*** TODO Open-seeded + reseed + dupre-revised regen :solo:
Phase 2. Initial state from seed() plus seedPkgmap for the non-org packages; all-tier reseed button with a scope-named overwrite warning, resetting non-org to their APPS defaults; regenerate dupre-revised.json. Gate: #selftest PASS; default-on-open equals seed(); artifact round-trip (regenerated dupre-revised.json imports back to the same seeded state); Chrome eyeball.
-*** TODO Seeding-engine test surface :solo:tests:
+*** TODO Seeding-engine test surface :solo:test:
Keep #seedtest, #selftest, the default-on-open check, the dupre-revised round-trip, node --check, and Chrome validation green.
-** TODO [#B] Dashboard keybinding changes :quick:
+** TODO [#B] Dashboard keybinding changes :quick:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-06
:END:
pressing g has should refresh. find another binding for Telegram.
-** TODO [#B] TTY-accessible personal C-; keymap :feature:ux:solo:quick:
+** TODO [#B] TTY-accessible personal C-; keymap :feature:solo:quick:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-05
:END:
@@ -232,19 +235,19 @@ Two smaller cleanups also fall out: the header comment claims TS/JS is "punted f
Open: helper home — keep =cj/--tests-in-buffer= in =dev-fkeys.el= (per L546 spec) or push it into =test-runner.el= (per the parallel "Fix up test runner" thread). Elisp "Run a test..." — drill into individual =ert-deftest= names, or keep the current regex-aggregate (=make test-name TEST=^test-<stem>-=).
-*** TODO [#B] Per-language test discovery helper :feature:tests:
+*** TODO [#B] Per-language test discovery helper :feature:test:
Build =cj/--tests-in-buffer= returning a list of test names; tree-sitter capture-then-filter for python/go/ts/js per the bug #79687 workaround in =dev-fkeys.el= L35–46; sexp scan for elisp =ert-deftest= forms.
-*** TODO [#B] F6 Run-a-test menu entry :feature:tests:
+*** TODO [#B] F6 Run-a-test menu entry :feature:test:
Add "Run a test..." to =cj/f6-test-runner= candidates; pre-select =cj/--last-test-run=; signal =user-error= "No tests found for <buffer>" when discovery returns nil.
-*** TODO [#B] M-F6 fast path :feature:tests:
+*** TODO [#B] M-F6 fast path :feature:test:
Bind =M-<f6>= to a thin wrapper that calls the same "Run a test..." path directly; release the reservation comment at =dev-fkeys.el:541=.
-*** TODO [#B] Buffer-local cj/--last-test-run :feature:tests:
+*** TODO [#B] Buffer-local cj/--last-test-run :feature:test:
Add the buffer-local var, set it on each "Run a test..." selection, use it as the completing-read default so a bare RET re-runs the last test.
-*** TODO [#B] TS/JS coverage status sync :docs:cleanup:
+*** TODO [#B] TS/JS coverage status sync
Update the =dev-fkeys.el= header comment (L33) — TS/JS is no longer punted; the cmd-builder at L384 emits vitest/jest. Document the prefer-vitest fallback.
** TODO [#B] Fix up test runner :bug:
@@ -751,11 +754,11 @@ Tie this into the existing coverage work:
- Tests cover adapter detection, command building, scope resolution, result
storage, and key interactive paths.
-** TODO [#B] Add Signal to the dashboard :quick:
+** TODO [#B] Add Signal to the dashboard :quick:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-01
:END:
-** TODO [#B] Messenger window/key unification :feature:ux:
+** TODO [#B] Messenger window/key unification :feature:
Spec: [[file:docs/design/messenger-unification-spec.org][messenger-unification-spec.org]] (Draft, 2026-06-11). One library (=cj-messenger-lib.el=) gives every messenger the same shape: chat windows rise from the bottom (the signel rule, generalized), C-c C-c confirms, C-c C-k cancels, C-c C-a attaches — dispatched per backend through a registry + minor mode. Signel already conforms (reference backend); telega and slack join in phases 2-3; ERC later. All eight decisions settled 2026-06-11 (cancel closes an idle window; telega's filter-cancel shadow accepted; slack rooms join the bottom rule). Spec held open — Craig has more ideas to fold in before it's marked Ready.
** TODO [#B] cj/undo-kill-buffer off-by-one on plain invocation :bug:quick:solo:
@@ -1112,56 +1115,56 @@ Full findings delivered as handoffs to each repo's inbox/ (2026-06-12-0057-from-
- chime (10 findings; suite green; the 2026-06-11 watchdog handoff VERIFIED landed in full): lookahead vars never injected into the async child (documented feature silently capped at 8 days — one-line fix); days-until-event nil crash on mixed timed/all-day events; stale-callback race after watchdog interrupt (generation counter needed); default test run prints green integration banner over "Ran 0 tests".
- emacs-wttrin (10 findings; ~56 ERT files, CI; the face-flood reminder VERIFIED resolved — test 8f3c770 + fix c5e5e1d, reminder cleared from notes.org): no network timeouts (wttr.in stalls hang the loading buffer); error-path response-buffer leak; non-favorite cache never expires; 17 unreleased commits incl. two features — tag v0.4.0.
-** PROJECT [#B] Implement ai-kb :feature:ai:kb:
+** PROJECT [#B] Implement ai-kb :feature:
Build v1 of the AI knowledge base per [[file:docs/design/ai-kb.org][docs/design/ai-kb.org]] (Ready; six reviews incorporated, all decisions resolved 2026-05-24). Step 1 splits into 1a (the safe write path — minimum usable) and 1b (retrieval, maintenance, push), since =remember= depends on =index=+=lint= and the adapter depends on =remember=. Step 2 is the Emacs layer: a full org-roam profile on switch, the human-edit safety model (same write path as the agent), and the browsing surface. Step 3 and the LLM-Wiki layer are vNext. Children are ordered by build sequence; the server bootstrap is the prerequisite.
-*** TODO [#B] ai-kb bare repo on cjennings.net :ai-kb:
+*** TODO [#B] ai-kb bare repo on cjennings.net
Prerequisite, one-time server bootstrap (not doable by the local script): =sudo git init --bare /var/git/ai-kb.git= + chown on cjennings.net. Leave the github-mirror hook OFF — this repo is private. Required before every per-machine clone.
-*** TODO [#B] ai-kb store + contract + seed :ai-kb:
+*** TODO [#B] ai-kb store + contract + seed
Step 1a. Clone =git@cjennings.net:ai-kb.git= to =~/.local/share/ai-kb=. Author =AGENT_CONTRACT.org= (canonical repo-resident contract: node format, write protocol, operations, routing) and seed =index.org= + a README/index node with a generated =:ID:=. Node format per spec — a *required* one-line =:SUMMARY:= (the index/query read it straight, no inference/LLM), provenance (=:CREATED_BY:/:CONFIDENCE:/:VISIBILITY:/:SOURCE:/:STATUS:=), =:PROJECTS:= slugs, type filetags, relation labels. Define the durable external-pointer format as *ID-first*: =ai-kb: <Title> (<UUID>)=, resolved by ID with title fallback (filenames can change in curation).
-*** TODO [#B] ai-kb CLI 1a: index, lint, remember, doctor :ai-kb:
+*** TODO [#B] ai-kb CLI 1a: index, lint, remember, doctor
Step 1a. Shell wrapper calling Emacs for org work — =emacsclient= when a daemon is up, =emacs --batch= fallback, lint+index in *one* invocation per =remember=. =index= regenerates =index.org= from node properties incl. =:SUMMARY:= (never hand-maintained); the index references nodes as plain =Title (UUID)= text, never =[[id:]]= links, and is excluded from the scan so it can't manufacture backlinks or hide orphans. =lint= = org-lint fatal checks + duplicate IDs + broken id-links (excl =raw/= + index) + missing required props (incl =:SUMMARY:=) + bad project slugs + stale/incomplete index + credential scan of nodes *and* =raw/= text files (binaries skipped). =remember= = the write protocol: fetch + =pull --ff-only= (abort on diverge/dirty), write, regenerate index, then run the *full =ai-kb lint=* over the change as the commit gate (not just node org-lint — this is the safety boundary), commit locally, =flock=; no push. =doctor= / =status= = health + push-state + raw-dir-size report (repo, private remote, CLI on PATH, =graphviz= if the map needs it, adapter linked, db buildable, no secrets, "ahead N"/"push failed"/"diverged"); =status= is the fast non-diagnostic mode for the dashboard/nudge.
-*** TODO [#B] claude-rules/ai-kb.md adapter :ai-kb:
+*** TODO [#B] claude-rules/ai-kb.md adapter
Step 1a. Global L1 rule in rulesets pointing at the repo-resident =AGENT_CONTRACT.org=: path, routing (T1/T2/T3 tiers; per-project =MEMORY.md= shrinks to ID-first pointers into ai-kb), proactive + contradiction rules, concrete "read the index first" triggers, link-grep recipes, "use =ai-kb remember=, never bypass =ai-kb lint=", one-line nudge on unpushed commits / recorded push rejection. =make install= symlinks it into =~/.claude/rules/=.
-*** TODO [#B] ai-kb provisioning: setup-ai-kb.sh + make ai-kb-init :ai-kb:
+*** TODO [#B] ai-kb provisioning: setup-ai-kb.sh + make ai-kb-init
Step 1a (core; the timer-install line is added with 1b). Idempotent =scripts/setup-ai-kb.sh=: clone (or init+add-remote on first machine), seed, install the CLI on PATH, =ai-kb index=, =ai-kb doctor=. =make ai-kb-init= wraps it. The one-time server bootstrap stays a separate documented step.
-*** TODO [#B] ai-kb Step-1a tests :ai-kb:tests:
+*** TODO [#B] ai-kb Step-1a tests :test:
Write-path: a write with the remote unreachable still commits locally and does not error; =flock= serializes concurrent =remember=; each org-lint *fatal* check (malformed drawer, missing/dup =:ID:=, invalid required property, missing =#+title:=, unparseable org) rejects the commit, a style warning does not; a node missing =:SUMMARY:= fails lint; =remember= aborts the commit when the *full* lint fails (stale index, broken link, secret in a node or =raw/= text file); the credential scan skips binaries. Index: regen from a fixture produces expected entries; an out-of-band node appears only after regen; a node referenced only by =index.org= still reports as an orphan (the index is not a backlink source). Link recipes: backlink (excl =raw/= + index) + forward correct. Provisioning (bats): idempotent, valid =:ID:= + =:SUMMARY:=, =doctor= passes.
-*** TODO [#B] ai-kb CLI 1b: query, curate, sync :ai-kb:
+*** TODO [#B] ai-kb CLI 1b: query, curate, sync
Step 1b. =query <context>= with a *testable contract*: plain-text default + =--json=; fields title/ID/summary/projects/status/updated/path + *match reason*; searches index rows + title/tags/properties/body; ranks by lexical score — sum of each matched field's weight, counted once per field: title 100, tag/project/status 50 each, summary 20, body 5; no term-frequency weighting in v1 — with most-recently-updated (=:UPDATED:=) only as the *tie-break* on equal scores (recency alone buries stable old preferences); default max-results; =raw/= paths only as source references; exit codes for no-match / invalid KB / lint-index failure. =show <id-or-title>= (resolve ID-first, print the node) and =backlinks <id>= (excl =raw/= + index) as the inspection primitives the Emacs commands wrap. =curate --dry-run= (four buckets; also flags orphan =raw/= captures and any =raw/= file over 256 KB; destructive ops human-only). =sync= (=org-roam-db-sync= against ai-kb) only when the db is missing/stale or forced.
-*** TODO [#B] ai-kb push timer + failure observability :ai-kb:
+*** TODO [#B] ai-kb push timer + failure observability
Step 1b. =ai-kb-push.timer= + =ai-kb-push.service= =systemd --user= units: push only if ahead, ~15 min; installed + =enable --now= by the setup script (add this line to =setup-ai-kb.sh=). A failed push is logged to a state file (=$XDG_STATE_HOME/ai-kb=), never fatal; surfaced by =ai-kb doctor= and the adapter's startup nudge.
-*** TODO [#B] ai-kb-curate workflow in rulesets :ai-kb:
+*** TODO [#B] ai-kb-curate workflow in rulesets
Step 1b. =~/code/rulesets/.ai/workflows/ai-kb-curate.org= — human-gated curation: the four buckets, node-count trigger (nudge at 150 nodes, re-fire every +50), =:LAST_CURATED:= rotation, pointer-integrity (merge/supersede changes the canonical ID, so grep inbound =[[id:]]= + =MEMORY.md= =ai-kb: ... (UUID)= refs and repoint before deleting). Surfaced by =ai-kb doctor= + session startup when due.
-*** TODO [#B] ai-kb Step-1b tests :ai-kb:tests:
+*** TODO [#B] ai-kb Step-1b tests :test:
=query --json= returns the specified fields (incl. match reason)/exit-codes on a fixture KB and =raw/= appears only as a source ref; a title match outranks a body-only match with recency only breaking ties (an old preference is not buried under a newer body-only hit); a simulated push failure is recorded to the state file and surfaced by =ai-kb doctor= / =status=. Performance (=:perf= tag): 100- and 1,000-node fixtures keep =index=/=query=/=lint=/=remember= under a stated time budget (catches an accidental per-check Emacs startup or an O(n²) scan).
-*** TODO [#B] Emacs: org-roam ai-kb profile + switch :ai-kb:
+*** TODO [#B] Emacs: org-roam ai-kb profile + switch
Step 2.
=org-roam-config.el=: =cj/org-roam-switch-to-ai-kb= / =cj/org-roam-switch-to-personal= install a full org-roam *profile*, not a two-variable swap — dir + =org-roam-ai.db= + =org-roam-file-exclude-regexp= (=raw/= + =index*.org=), and dailies, capture templates, topic/project/recipe find wrappers, and the agenda/refile + completed-task→daily hooks all rescoped or neutralized so ai-kb nodes never leak into personal journals/agenda. Restore everything exactly on exit; re-assert personal state at startup (abnormal-exit safety). =cj/ai-kb-db-sync= syncs only when the db is missing/stale or forced, with a status indicator.
-*** TODO [#B] Emacs: ai-kb edit safety (same write path) :ai-kb:
+*** TODO [#B] Emacs: ai-kb edit safety (same write path)
Step 2. An =ai-kb= minor mode whose =after-save-hook= runs the agent's post-write sequence under =flock= — =ai-kb index=, full =ai-kb lint=, commit, push-state update — so a human Emacs edit can't bypass index/lint/commit. One write path for both agent and human. Failure UX: the save always writes to disk and the buffer stays editable (never read-only/blocked); on lint failure it does *not* commit, pops findings to a =*ai-kb-lint*= buffer (no focus steal), and shows the uncommitted-failing state in the modeline + dashboard — Craig fixes and re-saves, a clean save commits. Recursion guard, two layers: the mode's activation predicate excludes =index*.org= + =raw/=, and the pipeline binds a re-entrancy flag (=cj/ai-kb--in-pipeline=) the hook early-returns on; index regen prefers =write-region= over =save-buffer=.
-*** TODO [#B] Emacs: ai-kb browsing surface :ai-kb:
+*** TODO [#B] Emacs: ai-kb browsing surface
Step 2. =cj/ai-kb-dashboard= (status banner: active KB, node count, unpushed commits, push-failure state, curation due, last index/sync), =cj/ai-kb-find-node= (=org-roam-node-find= in the ai-kb profile), =cj/ai-kb-search= (=ai-kb query= or scoped =consult-ripgrep=), =cj/ai-kb-show-node= (resolve ID-first, open), =cj/ai-kb-backlinks= (excl =raw/= + index), =cj/ai-kb-map= (built-in =org-roam-graph= *first* — the profile's exclude regexp already keeps =raw/= + index out of the db, so the graph inherits the right scope; custom DOT export only if project/tag/status filtering proves necessary; =graphviz= dep). Simple wrappers over the CLI primitives where possible.
-*** TODO [#B] Emacs: ai-kb keybindings + which-key :ai-kb:
+*** TODO [#B] Emacs: ai-kb keybindings + which-key
Bind the switch + sync + browsing commands under the =C-c n= roam prefix (e.g. =C-c n a= → ai-kb, =C-c n A= → personal, a small transient for the browsing commands), avoiding the dense existing set; which-key labels.
-*** TODO [#B] Emacs: ai-kb Step-2 ERT tests :ai-kb:tests:
+*** TODO [#B] Emacs: ai-kb Step-2 ERT tests :test:
Profile: switch installs the ai-kb dir + db + exclude regexp and switch-back restores personal *exactly* — completed-task hook, agenda/refile finalize hook, dailies, and capture templates all untouched by ai-kb while switched; startup re-asserts personal state after a simulated abnormal exit. Edit path: a save in an ai-kb buffer runs index+lint+commit (a bad save surfaces the lint failure rather than committing). Sync runs only when stale.
-** PROJECT [#B] Architecture review follow-up from 2026-05-03 :refactor:nosync:
+** PROJECT [#B] Architecture review follow-up from 2026-05-03 :refactor:
High-level pass over =init.el=, =early-init.el=, and all 104 files in
=modules/=. The main theme: the config works, but load order, startup side
@@ -1178,7 +1181,7 @@ Review snapshot:
files. Caveat: 55 module files do not appear in the report at all, so the
real project confidence is lower than the raw percentage suggests.
-*** 2026-05-15 Fri Consolidate shared utility helpers :architecture:refactor:
+*** 2026-05-15 Fri Consolidate shared utility helpers :refactor:
CLOSED: [2026-05-15 Fri]
Helpers are scattered across feature modules where they were first needed.
@@ -1199,7 +1202,7 @@ Guidance:
coverage.
- Do not add heavy package dependencies to foundation helpers.
-**** DONE [#B] Write full utility consolidation design spec :architecture:refactor:
+**** DONE [#B] Write full utility consolidation design spec :refactor:
CLOSED: [2026-05-04 Mon]
Create a design document that inventories candidate helper extractions,
@@ -1292,7 +1295,7 @@ Done 2026-05-10:
commit =0f9e3087=) with three sanitizers: =cj/org-sanitize-body-text=,
=cj/org-sanitize-property-value=, =cj/org-sanitize-heading=.
-*** 2026-05-15 Fri Make coverage reporting account for untracked modules :tests:
+*** 2026-05-15 Fri Make coverage reporting account for untracked modules :test:
CLOSED: [2026-05-15 Fri]
The current coverage result is useful but easy to overread. =make coverage=
@@ -1365,7 +1368,7 @@ Done 2026-05-15:
- Updated =tests/test-coverage-summary.el= to assert the policy and the
displayed project-module percentage.
-*** 2026-05-15 Fri Add a lightweight architecture smoke test for startup contracts :tests:
+*** 2026-05-15 Fri Add a lightweight architecture smoke test for startup contracts :test:
CLOSED: [2026-05-15 Fri]
After the above refactors start, add one or two smoke tests that protect the
@@ -1391,7 +1394,7 @@ Done 2026-05-15:
- Focused tests passed for the new architecture smoke file and the affected
agenda/refile helpers.
-*** PROJECT [#A] Un tangle the eager =init.el= load graph :architecture:refactor:
+*** PROJECT [#A] Un tangle the eager =init.el= load graph :refactor:
=init.el= currently functions as the dependency graph by eagerly requiring
almost every module in a fixed order. That makes modules harder to test in
@@ -1400,7 +1403,7 @@ assumptions.
Spec: [[file:docs/design/init-load-graph.org][docs/design/init-load-graph.org]]
-**** 2026-05-25 Mon @ 07:59:20 -0500 Wrote full design spec for the =init.el= load-graph refactor :architecture:refactor:
+**** 2026-05-25 Mon @ 07:59:20 -0500 Wrote full design spec for the =init.el= load-graph refactor :refactor:
Create a design document that defines the target architecture, module
categories, migration phases, test strategy, acceptance criteria, and risk
@@ -1454,7 +1457,7 @@ Verified behavior-preserving by dumping every C-; binding before and after: iden
Related existing task: [#B] "Review and rebind M-S- keybindings".
-*** PROJECT [#A] Move package bootstrap out of =early-init.el= where possible :startup:refactor:
+*** PROJECT [#A] Move package bootstrap out of =early-init.el= where possible :refactor:
=early-init.el= currently handles package archives, package refresh, installing
=use-package=, and =use-package-always-ensure=. That is more than early startup
@@ -1496,13 +1499,13 @@ Expected outcome:
:END:
Parent task for the Emacs Signal client. Engine: signal-cli (linked secondary device). Front end: a fork of signel at =~/code/signel=, wired through =modules/signal-config.el=. Design: [[file:docs/design/signal-client.org][docs/design/signal-client.org]]. Child issues below.
-*** TODO [#C] signel--handle-error leaks request-buffer-map entries :bug:no-sync:
+*** TODO [#C] signel--handle-error leaks request-buffer-map entries :bug:quick:solo:
Surfaced during the JSON-RPC dispatch refactor audit. =signel--handle-error= reads =signel--request-buffer-map= by id but never =remhash='es the entry, so every error response leaves the request-id → buffer-name mapping behind for the life of the process. Low impact (the map clears on stop/start, and id collisions are unlikely at the counter scale), but unbounded growth in a long-lived session and inconsistent with how the new request-handler-map is cleaned up on error.
*** TODO [#B] Link command with QR :feature:
=cj/signel-link= wrapping =signal-cli link -n NAME=, capturing the =sgnl://linkdevice= URI and rendering it as a scannable QR (qrencode). Convenience for re-linking; the first link was done by hand this session.
-*** TODO [#D] Include Signal groups in the picker :feature:no-sync:
+*** TODO [#D] Include Signal groups in the picker :feature:
vNext after the 1:1 initiate-message flow is stable. Merge =listGroups= with =listContacts=, label groups distinctly, and preserve the current v1 behavior where the picker is contacts-only.
*** DOING [#B] Notify only for the unviewed conversation :feature:
@@ -1548,7 +1551,7 @@ Verified: (1) new contract test =test-signal-config-prefix-map-registered-under-
*** 2026-05-28 Thu @ 03:09:18 -0500 Chat buffer docks bottom 30% and C-c C-k cancels
=display-buffer-alist= entry in =modules/signal-config.el= matches =^\*Signel: = chat buffers and routes them through =display-buffer-at-bottom= with =window-height . 0.3=, so the chat docks to the bottom 30% of the frame. The signel fork's =signel-chat= switched from =switch-to-buffer= to =pop-to-buffer= so the rule can apply (=switch-to-buffer= ignores =display-buffer-alist=). =C-c C-c= was already bound to =signel--send-input= in the mode; =C-c C-k= now binds =signel--cancel-input=, a new fork helper that clears the editable region between =signel--input-marker= and =point-max= and then calls =quit-window=. Buffer stays alive so chat history above the marker survives revisits; cleared input means the next visit lands on a fresh prompt. Five ERT tests in =tests/test-signel-cancel-input.el= (clears pending, empty-area no-op, quit-window called, buffer preserved, keymap binding) and two new tests in =tests/test-signal-config.el= (entry shape + regex match set). Dotemacs commit 998e9c7a, fork commit df02d79.
-** DOING [#B] Migrate All Terminals From Vterm to Ghostel :terminal:ghostel:
+** DOING [#B] Migrate All Terminals From Vterm to Ghostel
:PROPERTIES:
:LAST_REVIEWED: 2026-06-04
:END:
@@ -1556,16 +1559,13 @@ Replace vterm with ghostel (libghostty-vt) as the single terminal engine across
Decisions D1-D7 are settled in the spec's Agreed-decisions section. Build order below; each phase stays green (suite + byte-compile) at every step.
-*** TODO [#B] Phase 2: rename ai-vterm→ai-term on ghostel :terminal:ghostel:
-Swap the 6 vterm call sites; F9 family on global + ghostel-mode-map; drop refuse-in-terminal guard (D4); preserve the tmux-suppression invariant. Rename engine-agnostic tests after green; rework coupled tests; add D4 + F12-excludes-agent regression tests.
-
-*** TODO [#B] Follow-up: theme ghostel ANSI faces in dupre :terminal:ghostel:dupre:
+*** TODO [#B] Follow-up: theme ghostel ANSI faces in dupre
D2 — set the 16 ghostel-color-* + ghostel-default faces in dupre-faces/palette.
-*** TODO [#B] Follow-up: evaluate ghostel-eshell + ghostel-compile :terminal:ghostel:eval:
+*** TODO [#B] Follow-up: evaluate ghostel-eshell + ghostel-compile
D3 — ghostel-eshell as eshell visual backend; ghostel-compile against F4 dev-fkeys.
-*** TODO [#B] Investigate ghostel selection/highlight color :terminal:ghostel:
+*** TODO [#B] Investigate ghostel selection/highlight color
Look at how selected text is highlighted in a ghostel buffer — the region face in =ghostel-copy-mode= and any live selection — surfaced during the copy-mode debugging. Check whether the highlight is legible against the dupre background and consistent with the rest of the config; if it needs theming, fold it in with D2 (theming the ghostel faces in dupre).
*** 2026-06-04 Thu @ 23:57:09 -0500 Phase 0 done: characterization baseline green
@@ -1610,7 +1610,7 @@ 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 [[file:docs/design/vterm-to-ghostel-migration-spec.org][docs/design/vterm-to-ghostel-migration-spec.org]] after incorporation. Verdict: =Ready=. No further blocking review notes; implementation can start from the phase plan and acceptance criteria in the spec.
-** DOING [#B] Module-by-module hardening :harden:nosync:
+** DOING [#B] Module-by-module hardening
:PROPERTIES:
:LAST_REVIEWED: 2026-06-05
:END:
@@ -1668,7 +1668,7 @@ Suggested review order:
6. Integrations and applications: mail, Slack, ERC, Elfeed, EWW, Dirvish,
PDF, Calibre, music, recording/transcription, AI/rest tooling.
-*** DOING [#B] Harden foundation modules :harden:
+*** DOING [#B] Harden foundation modules
Scope:
- =system-lib.el=
@@ -1763,7 +1763,7 @@ vc-follow-symlinks test use one copy. The backups test clears
=cj/backup-directory= first because it's a defvar that only recomputes when
unbound.
-**** TODO [#B] Move package bootstrap policy out of =early-init.el= :startup:refactor:
+**** TODO [#B] Move package bootstrap policy out of =early-init.el= :refactor:
=early-init.el= currently handles performance/debug setup, package archive
construction, archive refresh policy, =use-package= installation, package
@@ -1789,7 +1789,7 @@ Pitfalls:
- Keep local repositories higher priority than online archives.
- Avoid prompting or refreshing archives during batch tests.
-**** TODO [#B] Decide and test package signature policy :security:startup:
+**** TODO [#B] Decide and test package signature policy
=early-init.el= sets =package-check-signature= to =nil= after package setup, with
an earlier commented emergency toggle for expired signatures. That may be
@@ -1873,7 +1873,7 @@ dir, empty dir, missing dir; reset path; select with prefix-arg
boundary paths for empty dir, missing dir, cancel (empty completion).
9 tests, all green.
-*** DOING [#B] Harden custom editing utility modules :harden:
+*** DOING [#B] Harden custom editing utility modules
Scope:
- =custom-buffer-file.el=
@@ -1902,7 +1902,7 @@ Completion review 2026-05-15:
destructive buffer/file keybinding policy, and explicit cross-module
autoload/require boundaries.
-**** TODO [#B] Harden external process launching in =external-open.el= and =media-utils.el= :security:refactor:
+**** TODO [#B] Harden external process launching in =external-open.el= and =media-utils.el= :refactor:
=external-open.el= and =media-utils.el= use shell command strings for launching
external applications:
@@ -1934,7 +1934,7 @@ Pitfalls:
**** 2026-05-23 Sat @ 03:41:00 -0500 Added media-utils coverage; external-open already covered
=external-open= already had three test files (=test-external-open-commands.el=, =test-external-open-lib-command.el=, =test-external-open-lib-launcher-p.el=). =media-utils.el= had none, so I added =test-media-utils.el= (8 cases): player availability from =cj/media-players=, the play command-builder (direct vs yt-dlp -g stream wrap), and the missing-tool error paths for the player, =yt-dlp=, and =tsp=. All process/exec boundaries mocked. Added in the test-media-utils commit.
-**** TODO [#B] Audit destructive buffer/file keybindings for confirmation policy :ux:
+**** TODO [#B] Audit destructive buffer/file keybindings for confirmation policy
=cj/buffer-and-file-map= includes destructive operations under =C-; b=,
including delete file, erase buffer, clear top, clear bottom, and revert. Some
@@ -2008,7 +2008,7 @@ added since the new helper uses =cl-some=.
**** 2026-05-23 Sat @ 03:44:50 -0500 Covered flyspell-and-abbrev testable seams
Added =test-flyspell-and-abbrev.el= (8 cases): =cj/--require-spell-checker= (PATH gate, mocked), =cj/find-previous-flyspell-overlay= against synthetic overlays (closest-previous match, non-flyspell skipped, nil when none), and =cj/flyspell-on-for-buffer-type= (prog-mode -> flyspell-prog-mode, text-mode -> flyspell-mode). Left =cj/flyspell-then-abbrev= to manual testing — pinning its flyspell-UI orchestration would mean mocking flyspell internals rather than our logic. Added in the test-flyspell-abbrev commit.
-*** DOING [#B] Harden UI and navigation modules :harden:
+*** 2026-06-12 Fri @ 07:14:11 -0500 Hardened UI and navigation modules
Scope:
- =ui-config.el=
@@ -2156,7 +2156,7 @@ catches the "already-loaded when this module re-evaluates" case --
it checks =advice-member-p= so it doesn't stack the advice on every
re-eval.
-*** DOING [#B] Harden Org workflow modules :harden:
+*** DOING [#B] Harden Org workflow modules
Scope:
- =org-config.el=
@@ -2205,9 +2205,9 @@ Completion review 2026-05-15:
**** 2026-05-23 Sat @ 04:18:44 -0500 Split personal calendar config out of calendar-sync.el
=calendar-sync.el= now defaults =calendar-sync-calendars= to nil and loads the real plists from =calendar-sync-private-config-file= (an ignored file), so the engine carries no private feed tokens in source. =calendar-sync-status= / =calendar-sync-start= report missing config without erroring, and agenda startup is unaffected (tests/test-calendar-sync-no-config-startup.el). Rotating the previously-committed feed URLs remains a manual credential action — tracked under the L2557 calendar-sync hardening finding.
-**** PROJECT [#B] Normalize Org agenda/refile cache lifecycle :perf:refactor:
+**** 2026-06-12 Fri @ 07:14:11 -0500 Normalized Org agenda/refile cache lifecycle
-Two of three children are done (shared cache helper extracted, idle timers gated). Still open: the directory-scan-failure visibility child below.
+All three children landed: shared cache helper extracted, idle timers gated, and directory-scan failures surfaced instead of hidden.
***** 2026-05-23 Sat @ 04:18:44 -0500 Extracted a shared cache helper
=cj-cache-lib.el= now provides =cj/cache-valid-p=, =cj/cache-building-p=, and =cj/cache-value-or-rebuild=, consumed by both =org-agenda-config.el= and =org-refile-config.el=; the contract is documented in =docs/design/cache-helper-design.org=. The agenda and refile public commands are unchanged.
@@ -2223,7 +2223,7 @@ Both cache builders' =run-with-idle-timer= calls are wrapped in =(unless noninte
=org-babel-config.el= set =org-confirm-babel-evaluate= to nil globally, so every source block in every Org file (cloned repos, downloaded notes, web clips) ran without confirmation. Changed the default to =t= (confirm before running). Replaced the old =babel-confirm= command (which reported, and toggled only with a prefix arg) with =cj/org-babel-toggle-confirm=, a plain toggle bound to =C-; k= for flipping confirmation off in trusted files and back on. 3 ERT tests cover the toggle both directions plus the binding.
-**** TODO [#B] Rebind babel-confirm toggle off =C-; k= :keybinding:solo:discuss:
+**** TODO [#B] Rebind babel-confirm toggle off =C-; k=
=cj/org-babel-toggle-confirm= landed on =C-; k= as a placeholder. Pick a permanent home — likely under an Org-specific prefix rather than the global =C-;= map.
@@ -2296,7 +2296,7 @@ org-capture-config.el and org-drill-config.el each scanned =drill-dir= with an i
**** 2026-05-23 Sat @ 03:48:50 -0500 Fixed java structure-template typo and pinned the aliases
=("java" . "src javas")= expanded to a bogus =#+begin_src javas=; corrected it to =src java=. Added =test-org-babel-config-structure-templates.el=, which requires the module then org-tempo (firing the deferred :config) and asserts =bash=, =zsh=, =el=, =py=, =json=, =yaml=, =java= each map to the intended src language. Fixed in the org-babel commit.
-**** TODO [#B] Make org-contacts/Mu4e boundaries explicit :cleanup:refactor:
+**** TODO [#B] Make org-contacts/Mu4e boundaries explicit :refactor:
=org-contacts-config.el= defines helpers that call Mu4e functions when the
current major mode is a Mu4e mode, and the =use-package org-contacts= block is
@@ -2313,7 +2313,7 @@ Expected outcome:
- Add a smoke test for loading =org-contacts-config.el= without Mu4e stubs if
practical.
-**** TODO [#B] Add an Org workflow health check command :feature:ux:solo:discuss:
+**** TODO [#B] Add an Org workflow health check command :feature:solo:
Several Org workflow modules depend on personal paths, optional external tools,
and local package checkouts. Failures currently show up at command time in
@@ -2335,7 +2335,7 @@ Recommended improvement:
New =test-org-capture-templates-integrity.el= loads the cleanly-loadable capture modules (=org-capture-config=, =quick-video-capture=, =org-contacts-config=), applies their lazy additions, and asserts no two templates share a dispatch key and that every symbol-valued file target resolves to a non-empty path string. Literal-string targets (the video template's no-save =(file "")=) and lambda targets (drill file pickers) are excluded. Webclipper templates need org-web-tools at registration time, so they stay covered by their own test rather than this batch smoke test. Mutation-checked that the uniqueness assertion flags a duplicate key. Commit =2e3905c7=.
-**** TODO [#B] Document Org workflow module ownership and load boundaries :docs:refactor:solo:discuss:
+**** TODO [#B] Document Org workflow module ownership and load boundaries :refactor:solo:
The Org workflow is spread across many modules with overlapping responsibilities:
capture templates, keymaps, org-protocol handlers, refile/agenda target
@@ -2400,7 +2400,7 @@ direct-insert no-op-outside-header path round it out. mail-abbrev-in-expansion-h
the mode actions, and cj/get-all-contact-emails are stubbed, so the run is
headless with no mu4e/org-contacts dependency.
-*** DOING [#B] Harden programming workflow modules :harden:
+*** DOING [#B] Harden programming workflow modules
Scope:
- =prog-c.el=
@@ -2456,7 +2456,7 @@ value. Forcing a test there would be coverage theater.
tree-sitter, and the task itself says to wait for the LSP/tree-sitter policy
tasks to land before fixing its assertions. Its smoke coverage rides with those.
-**** TODO [#B] Revisit F4 project classification vs actual project capabilities :ux:
+**** TODO [#B] Revisit F4 project classification vs actual project capabilities
=dev-fkeys.el= classifies a project as =interpreted= if it has
=pyproject.toml=, =requirements.txt=, =Pipfile=, or =package.json=, even when it
@@ -2473,7 +2473,7 @@ Expected outcome:
- Keep the current "interpreted markers win" behavior only if that remains the
intentional UX after trying it in mixed Python/Node projects.
-**** PROJECT [#B] Consolidate LSP ownership across programming modules :architecture:refactor:
+**** PROJECT [#B] Consolidate LSP ownership across programming modules :refactor:
LSP setup is currently split across =prog-general.el=, =prog-lsp.el=, and each
language module. There are multiple =use-package lsp-mode= forms and some
@@ -2500,7 +2500,7 @@ Pitfalls:
- Do not accidentally re-enable UI/doc/sideline behavior that was explicitly
disabled for performance.
-***** TODO [#B] Add a startup smoke test for LSP config resolution :solo:
+***** TODO [#B] Add a startup smoke test for LSP config resolution :quick:solo:
Keep this narrow. A useful test can require the LSP-related modules with mocked
=use-package= side effects and assert that:
@@ -2508,7 +2508,7 @@ Keep this narrow. A useful test can require the LSP-related modules with mocked
- no duplicate hook entries are installed for the same mode,
- =lsp-enable-remote= remains nil.
-**** TODO [#B] Gate tree-sitter grammar auto-install behind an explicit policy :startup:
+**** TODO [#B] Gate tree-sitter grammar auto-install behind an explicit policy
=prog-general.el= sets =treesit-auto-install= to =t=. That means opening a file
can trigger grammar download/build/install behavior. This is convenient on a
@@ -2529,7 +2529,7 @@ depends on it.
=cj/git-clone-clipboard-url= shelled out via =shell-command= and derived the dir with =file-name-nondirectory=, which mishandled scp-style SSH with no slash (=git@host:repo.git= → =git@host:repo=) and silently did nothing on a failed clone. Now clones as a direct =git= process (=call-process=, no shell) with =clone -- url dir= (so a =-=-leading URL can't be read as a flag); the destination comes from =cj/--git-clone-dir-name= (last component split on =/= and =:=, handling HTTPS, scp/ssh:// SSH, local paths); validates non-empty clipboard + writable target dir + non-existing destination; surfaces a non-zero git exit as a =user-error= with the =*git-clone*= output. Tests cover the deriver across schemes + empty-clipboard + clone-failure. Commit =35e4d701=.
-**** TODO [#B] Decide whether auto-executable shell scripts should be opt-in :ux:
+**** TODO [#B] Decide whether auto-executable shell scripts should be opt-in
=prog-shell.el= adds a global =after-save-hook= that sets executable bits on any
saved file with a shebang. This is convenient, but it silently changes file
@@ -2642,7 +2642,7 @@ code's =bound-and-true-p= can't see; declaring both as special makes
Full suite: =make test= exits 0; 468 lines of output with =ALL UNIT
TESTS PASSED= banner; no regressions.
-*** DOING [#B] Harden integrations and application modules :harden:
+*** DOING [#B] Harden integrations and application modules
Scope:
- AI/rest: =ai-config.el=, =ai-conversations.el=, =restclient-config.el=
@@ -2688,7 +2688,7 @@ Not done: the detached restart+reconnect (=nohup sh -c '... && emacsclient -c'=)
Resolved by removing the feature rather than hardening it. =cj/restclient-skyfi-buffer= opened =data/skyfi-api.rest= in a file-visiting buffer and rewrote the =:skyfi-key= line with the real key from authinfo, so an accidental save would persist the key to local disk (the file was gitignored and never tracked, so no repo/public-mirror exposure — local plaintext only). Deleted =cj/skyfi-api-key=, =cj/restclient--inject-skyfi-key=, =cj/restclient-skyfi-buffer=, the =C-; R s= binding, the two SkyFi test files, and the local =data/skyfi-api.rest= template. Generic restclient (=C-; R n=, =C-; R o=, restclient/restclient-jq) kept.
-**** TODO [#B] Reconcile mail image/privacy settings :privacy:
+**** TODO [#B] Reconcile mail image/privacy settings
=mail-config.el= documents blocked remote images and sets
=gnus-blocked-images=, but later enables both =mu4e-show-images= and
@@ -2715,7 +2715,7 @@ The protocol handler =setq= a global =cj/video-download-current-url= and the cap
Note: the sibling =org-webclipper.el= still uses the same global-mutation pattern (=cj/webclip-current-url= / =title=); a separate =:solo:= task tracks that.
-**** TODO [#B] Audit shell-command-heavy recording and dwim-shell workflows :security:refactor:
+**** 2026-06-12 Fri @ 07:14:11 -0500 Audited shell-command-heavy recording and dwim-shell workflows
=video-audio-recording.el= and =dwim-shell-config.el= are intentionally close to
the shell: pactl/ffmpeg/qpdf/7z/tesseract/media conversion commands are the
@@ -2763,7 +2763,7 @@ Stop ran =pkill -INT wf-recorder=, signalling every wf-recorder on the system in
The toggles ran =(file-name-directory location)= before =make-directory=, which returns the *parent* for a path without a trailing slash — so the selected directory went uncreated and ffmpeg failed to write into it. Both toggles now route the destination through =cj/recording--normalize-recording-dir= (expand + =file-name-as-directory=) and =make-directory= that, creating the selected directory itself (including names with spaces). Tests cover trailing-slash normalization, idempotence, spaces, and relative-to-absolute expansion. Commit =dc033c75=.
-**** TODO [#B] Make AI conversation persistence path-safe and project-aware :cleanup:refactor:
+**** TODO [#B] Make AI conversation persistence path-safe and project-aware :refactor:
=ai-conversations.el= has good pure helper seams but is currently untested in
this repo. The path slugging is simple and the save/load/delete commands operate
@@ -2777,7 +2777,7 @@ Expected outcome:
- Confirm autosave never writes partial prompt/response state to an unexpected
file after loading a different conversation.
-**** TODO [#B] Harden calendar sync operational behavior around the parser :data:refactor:
+**** TODO [#B] Harden calendar sync operational behavior around the parser :refactor:
=calendar-sync.el= has broad parser/recurrence coverage, but the operational
path around it still has startup, persistence, and fetch risks.
@@ -2857,7 +2857,7 @@ feature is broken end-to-end. Change the URL to
=http://localhost:8080/imp= (and consider switching the launch to
=browse-url= so the user's default protocol handler is respected).
-**** TODO [#B] Document or vendor strapdown.js CDN dependency in =markdown-preview= :cleanup:solo:discuss:
+**** TODO [#B] Document or vendor strapdown.js CDN dependency in =markdown-preview= :solo:
=cj/markdown-html= (=modules/markdown-config.el:48-51=) embeds a
=<script src="http://ndossougbe.github.io/strapdown/dist/strapdown.js">=
@@ -2949,19 +2949,19 @@ configuration (=text-config=, =diff-config=, =ledger-config=,
=games-config=, =mu4e-org-contacts-setup=, =telega-config=,
=httpd-config=, =org-agenda-config-debug=).
-** TODO [#C] theme-studio picker panel blends into the page :bug:theme-studio:ux:quick:solo:
+** TODO [#C] theme-studio picker panel blends into the page :bug:quick:solo:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-11
:END:
Craig, 2026-06-11 manual-test walk: the color picker's background is hard to distinguish from the page background. Give the picker panel a visibly distinct background or a highlighted border so it stands out. Pin with a gate asserting the picker element carries the distinct style.
-** TODO [#C] theme-studio Rust + Zig language previews :feature:theme-studio:solo:
+** TODO [#C] theme-studio Rust + Zig language previews :feature:
: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.
-** TODO [#C] theme-studio face-consistency check :feature:theme-studio:
+** TODO [#C] theme-studio face-consistency check :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-10
:END:
@@ -2973,29 +2973,29 @@ Rule taxonomy captured in [[file:docs/design/theme-studio-face-rules.org][docs/d
Bake into the tool (a lint surfaced in the UI) or run as a build-time check (seeds vs live deffaces via emacsclient).
-** TODO [#C] Color-family per-hex hint override :feature:theme-studio:
+** TODO [#C] Color-family per-hex hint override :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-11
:END:
For the ~1 color per palette that sits on a ramp-collision point (e.g. yellow+2 on the distinguished palette, which by every hex signal belongs to the olive ramp though its name says gold), automatic grouping cannot recover the designer's intent. Add a per-hex family override: drag a swatch to a different column, store the override keyed by hex (never the name, so renaming is still free), consult it after the LCCL clustering, and drop/mark-stale it when the hex changes substantially. Export stays mostly flat; only overrides are extra metadata. Both reviews recommend this exact shape; details in =~/color-sorting-fable.org= (§ "The irreducible case") and =~/color-sorting-codex.org= (§ "What to store").
-** TODO [#C] Internet radio now-playing song :feature:music:emms:solo:
+** TODO [#C] Internet radio now-playing song :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-11
:END:
Show the currently-playing song while streaming an internet radio station. Lives in =modules/music-config.el= (EMMS + MPV backend, M3U radio stations). The track title comes from the stream's ICY metadata — EMMS exposes it via =emms-track-description= / =emms-playing-time= and updates it on the metadata-change hook; MPV reports the ICY title too. Add an option to show the song in the minibuffer (e.g. echo on track change, or an on-demand command). Consider also a mode-line indicator as a second surface.
-** TODO [#C] Evaluate jamescherti essential-emacs-packages list :packages:research:quick:solo:
+** TODO [#C] Evaluate jamescherti essential-emacs-packages list :quick:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-11
:END:
Review [[https://www.jamescherti.com/essential-emacs-packages/][James Cherti's essential Emacs packages]] for anything worth installing. Cross-check each candidate against what is already in the config (=modules/= + =init.el=), skip the ones already present, and shortlist the genuinely new ones with a one-line rationale. Future-installation research, not a commitment to install.
-** TODO [#C] dupre-clear theme — contrast-first AAA sibling :feature:theme:dupre:
+** TODO [#C] dupre-clear theme — contrast-first AAA sibling :feature:
Build a new theme (working name "dupre-clear", final name TBD) that takes dupre's color identity and rebuilds it Prot's way: contrast-first, targeting WCAG AAA (~7:1 on the ground), where the in-progress dupre revision is mood/depth-first and lands at AA. Same hues (dupre blue, emerald, gold, terracotta, regal violet, mint) brightened to clear the AAA floor; same modus-style role mapping (blue keywords bold, gold functions, violet types, emerald strings, terracotta constants, silver default, warm-grey comments, metallic greys, navy + regal fills). Build the dupre revision first; this reuses its hue choices as the starting point.
Full design + methodology + starting palette + open questions in the spec: [[file:docs/design/dupre-clear-theme.org][docs/design/dupre-clear-theme.org]]. Key prerequisite/context: the dupre-redesign entry in =.ai/session-context.org= (the AA palette this brightens). Hardest slot: blue keywords (a deep dupre blue can't be AAA on near-black — decide brighten vs keep-AA-exception vs lift-the-ground).
-** TODO [#C] theme-studio terminal/ANSI colors :feature:theme-studio:
+** TODO [#C] theme-studio terminal/ANSI colors :feature:
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:
1. ANSI-16 panel. Map the 16 ANSI slots (black/red/green/yellow/blue/magenta/cyan/white + bright variants) to palette colors, with a preview, and export them so =build-theme.el= emits the =ansi-color-*= / =term-color-*= faces. This matters even in pure-GUI Emacs: colored shell output, compilation buffers, eshell, and vterm/eat all draw from these. Signals must line up with their ANSI slot (error red→ansi red, success→green, warning→yellow, info/link→blue) so a signal reads the same in a terminal.
@@ -3008,7 +3008,7 @@ Why this scope: the GUI and the normal terminal (foot + tmux, truecolor / ≥256
:LAST_REVIEWED: 2026-06-06
:END:
They should have the same UI paradigms and patters for consistency.
-** TODO [#C] Slack message buffers in a reused popup window :slack:ux:quick:
+** TODO [#C] Slack message buffers in a reused popup window :quick:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-05
:END:
@@ -3055,7 +3055,7 @@ Acceptance: structs defined, helpers callable in batch without EMMS loaded.
Depends on: none (start here).
-*** TODO [#B] Backend protocol + fake test backend :refactor:tests:
+*** TODO [#B] Backend protocol + fake test backend :refactor:test:
Define the backend plist contract (=:available-p :play :pause :resume :stop
:seek :volume :status :metadata=) and =cj/music-current-backend=. Add
=cj/music-state-change-functions= abnormal hook with the v1 event set
@@ -3068,7 +3068,7 @@ against a no-op playback flow.
Depends on: pure helpers + state structs.
-*** TODO [#B] Read-side state API + characterization tests :tests:refactor:
+*** TODO [#B] Read-side state API + characterization tests :test:refactor:
Implement =cj/music-playing-p=, =cj/music-paused-p=, =cj/music-current-track=,
=cj/music-playlist-state=, =cj/music-track-description=. Before rewriting
command bodies, add characterization tests against current behavior for
@@ -3124,7 +3124,7 @@ characterization tests still green; Dirvish =+= add path covered.
Depends on: playlist major mode + mpv backend.
-*** TODO [#B] EMMS removal + parity walk :cleanup:tests:
+*** TODO [#B] EMMS removal + parity walk :test:
Remove =cj/emms--setup=, the on-demand EMMS loader, and the =use-package emms=
block. Add the EMMS-free batch-load smoke test (=music-config.el= requires
clean without EMMS installed). Run the 22-step parity walk from design
@@ -3137,7 +3137,7 @@ walk recorded as a completion log entry under the parent task.
Depends on: command + Dired/Dirvish rewire.
-** TODO [#C] music-config option-combination audit + tests :tests:harden:solo:
+** TODO [#C] music-config option-combination audit + tests :test:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-06
:END:
@@ -3150,7 +3150,7 @@ Part 2 — combinatorial test coverage. Use the =/pairwise-tests= skill: identif
The recent F10 side-window-height-fraction work and the EMMS-free refactor candidate ("Implement EMMS-free music-config architecture" above) are both natural near-term touchpoints — best to land this audit before the EMMS swap so the new architecture inherits a clean option spec.
-** TODO [#C] gptel-magit activation fails on velox :bug:ai:quick:
+** TODO [#C] gptel-magit activation fails on velox :bug:quick:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-01
:END:
@@ -3163,7 +3163,7 @@ Reproduce:
Next step: check the installed gptel version (=(assq 'gptel package-alist)= or =M-x package-list-packages=), update gptel to >= 0.9.8, then re-evaluate gptel-magit activation. If gptel was pinned/held on velox, reconcile the pin against the gptel-magit dependency.
-** TODO [#C] GPTel Work :feature:ai:
+** TODO [#C] GPTel Work :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-01
:END:
@@ -3190,7 +3190,7 @@ Design doc: [[file:docs/design/mcp-el-gptel-integration.org][docs/design/mcp-el-
Commit =54d231be=. Sections 1 (constants + defcustoms) and 3 (pure helpers) of the seven-section outline. 41 ERT tests, all green. Refactor audit caught two duplications during Phase 4 and folded them into the same commit (=cj/mcp--get-server-entry= and =cj/mcp--name-matches-p=). Phase 1.5 (confirmation contract) is next.
-**** TODO [#C] Phase 1.5 -- GPTel confirmation contract :mcp:
+**** TODO [#C] Phase 1.5 -- GPTel confirmation contract
*Goal:* flip =gptel-confirm-tool-calls= to ='auto= and gate the existing local tools that need it.
@@ -3210,7 +3210,7 @@ Commit =54d231be=. Sections 1 (constants + defcustoms) and 3 (pure helpers) of
*Exit:* tests green. Manual smoke: open GPTel, call a gated tool, confirm prompt appears. Call =git_log=, no prompt.
-**** TODO [#B] Phase 2 -- Compat layer + registration pipeline (fake inventory) :mcp:
+**** TODO [#B] Phase 2 -- Compat layer + registration pipeline (fake inventory)
*Goal:* implement the mcp.el compat wrappers and the tool-registration pipeline against stubbed =mcp-server-connections=.
@@ -3224,7 +3224,7 @@ Commit =54d231be=. Sections 1 (constants + defcustoms) and 3 (pure helpers) of
*Exit:* with a stubbed =mcp-server-connections=, registration produces correctly prefixed =mcp__SERVER__TOOL= entries in =gptel-tools=; closures call =mcp-call-tool SERVER REMOTE-NAME= (verified by stubbing =mcp-async-call-tool=); deregistration removes only MCP-owned tools and leaves a pre-populated local =git_log= entry intact; re-registration replaces function pointer without duplicating menu entries; confirm overrides win over patterns.
-**** TODO [#B] Phase 3 -- Async state machine + timer-race timeout wrapper :mcp:
+**** TODO [#B] Phase 3 -- Async state machine + timer-race timeout wrapper
*Goal:* implement the lifecycle state machine and the per-call timer-race timeout.
@@ -3237,7 +3237,7 @@ Commit =54d231be=. Sections 1 (constants + defcustoms) and 3 (pure helpers) of
*Exit:* =cj/mcp-ensure-started= returns in <100 ms with delayed-callback stubs; stall timer fires for stuck servers; timer-race wrapper handles all three orderings (MCP-first, timer-first, late-MCP-after-timer); async error path (=:error-callback= without inited callback) reaches =failed= state via polling.
-**** TODO [#B] Phase 4 -- First real connection (drawio or slack-deepsat) :mcp:
+**** TODO [#B] Phase 4 -- First real connection (drawio or slack-deepsat)
*Goal:* wire one real no-auth server end-to-end against actual mcp.el and prove the stubbed Phase 3 behavior matches reality.
@@ -3251,7 +3251,7 @@ Commit =54d231be=. Sections 1 (constants + defcustoms) and 3 (pure helpers) of
*Exit:* manual smoke -- =C-; a t= opens GPTel without blocking; within 30 s, drawio (or slack-deepsat) tools appear in =gptel-menu= grouped by category; calling a tool returns expected output; killing the subprocess externally surfaces as =failed= in =cj/mcp--server-status=.
-**** TODO [#B] Phase 5 -- Status UX + commands + doctor (static) :mcp:
+**** TODO [#B] Phase 5 -- Status UX + commands + doctor (static)
*Goal:* ship the full server-management UX so partial-availability and failures are visible.
@@ -3267,7 +3267,7 @@ Commit =54d231be=. Sections 1 (constants + defcustoms) and 3 (pure helpers) of
*Exit:* all keymap bindings work; audit buffer surfaces failed servers prominently; doctor identifies each scenario in the manual test matrix; status command shows the right state for each phase transition.
-**** TODO [#B] Phase 6 -- HTTP servers (linear, notion) :mcp:
+**** TODO [#B] Phase 6 -- HTTP servers (linear, notion)
*Goal:* add the two HTTP-transport servers with in-protocol OAuth.
@@ -3280,7 +3280,7 @@ Commit =54d231be=. Sections 1 (constants + defcustoms) and 3 (pure helpers) of
*Exit:* first connect surfaces the OAuth URL through the recovery pattern; after browser handshake completes, subsequent connects succeed without prompt; live-auth-check correctly identifies a deliberately revoked token; both servers appear ready in the audit buffer.
-**** TODO [#B] Phase 7 -- Env-dependent stdio servers (figma, google-*) :mcp:
+**** TODO [#B] Phase 7 -- Env-dependent stdio servers (figma, google-*)
*Goal:* add the remaining five env-dependent servers.
@@ -3294,7 +3294,7 @@ Commit =54d231be=. Sections 1 (constants + defcustoms) and 3 (pure helpers) of
*Exit:* all 9 servers reach =ready= state on a clean machine. Sentinel-grep check across status / audit / hub / errors / audit-log shows zero secret leakage. Doctor's live-auth covers each auth class (oauth, token, args-token, in-protocol, local, none).
-**** TODO [#B] Phase 8 -- Privacy + audit polish :mcp:
+**** TODO [#B] Phase 8 -- Privacy + audit polish
*Goal:* land the final UX polish and documentation.
@@ -3727,7 +3727,7 @@ no command yet.
Depends on: none -- start here.
-*** TODO [#B] ERT coverage for the pure helpers :feature:tests:
+*** TODO [#B] ERT coverage for the pure helpers :feature:test:
Normal/Boundary/Error tests for every helper from the prior sub-task,
matching the design's testing section.
@@ -3788,7 +3788,7 @@ untouched.
Depends on: review-buffer major mode + parser.
-*** TODO [#B] Interactive command + smoke test :feature:tests:
+*** TODO [#B] Interactive command + smoke test :feature:test:
Thin =cj/dev-setup-project= interactive wrapper: resolve project root via
projectile, run detection, build proposal, render the review buffer, pop to
it. One smoke test against a prepared temp project asserting the expected
@@ -3802,7 +3802,7 @@ buffer; =C-c C-c= writes the expected files.
Depends on: writer + status diff + projectile cache reset.
-*** TODO [#B] Resolve open questions + design follow-ups :cleanup:
+*** TODO [#B] Resolve open questions + design follow-ups
Three design questions to close before / during implementation: (a) include
=make coverage= target in starter Makefile? (b) project-wide override file
=.cj-dev-setup.el=? (c) Cargo/pom detection.
@@ -3897,7 +3897,7 @@ The core functionality is implemented but needs debugging before it's production
3. Refine toggle behavior based on testing
4. Document the final keybindings and workflow
-** TODO [#C] Build an Org-native API workspace :feature:tests:
+** TODO [#C] Build an Org-native API workspace :feature:test:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-02
:END:
@@ -4291,7 +4291,7 @@ F2 is the universal preview key. Currently bound only in markdown-mode (markdown
Keep the binding mode-local so F2 stays available as a global candidate where no preview makes sense.
-** TODO [#C] Localrepo Documentation :feature:docs:localrepo:
+** TODO [#C] Localrepo Documentation :feature:
:PROPERTIES:
:LAST_REVIEWED: 2026-06-05
:END:
@@ -4305,11 +4305,11 @@ The four limitations the docs cover (each spun out below as its own task):
- Native-comp =.eln= cache (Emacs-version-specific; invalidated by version bumps)
- System-tool deps (=ripgrep=, =fd=, =pandoc=, =prettier=, =pyright=, etc.; flagged at load by =cj/executable-find-or-warn=, not packageable via =package.el=)
- Refresh / update story (no dedicated script today; ad-hoc =cp= from the elpa mirrors)
-*** TODO [#C] Design doc — docs/design/localrepo.org :docs:
+*** TODO [#C] Design doc — docs/design/localrepo.org
Write the design doc: tier model, priorities, install path, refresh story, all four limitations with cross-links to the follow-up tasks below.
-*** TODO [#C] README — .localrepo/README.org :docs:
+*** TODO [#C] README — .localrepo/README.org
Write the README at the artifact: short prose entry point summarizing the tier model, pointing at =docs/design/localrepo.org= for full detail. This is what =early-init.el='s commentary header links to.
-*** TODO [#C] Commentary header in early-init.el :docs:
+*** TODO [#C] Commentary header in early-init.el
Add a Commentary-section header in =early-init.el= pointing at =.localrepo/README.org= for usage and =docs/design/localrepo.org= for architecture. Sits at the top of the localrepo block (around L130).
** TODO [#C] TRAMP/dirvish "?" for remote dates — verify the fix per host :bug:
:PROPERTIES:
@@ -4358,28 +4358,28 @@ When =(env-terminal-p)=, =(setenv "GPG_TTY" (shell-command-to-string "tty"))= so
*** TODO [#C] gpg-agent updatestartuptty refresh in terminal :feature:
The current =call-process= to "gpg-connect-agent updatestartuptty /bye" runs unconditionally; keep it for GUI, and re-fire it on terminal entry so the agent re-binds to the current tty.
-*** TODO [#C] ERT tests for terminal vs GUI pinentry branching :tests:
+*** TODO [#C] ERT tests for terminal vs GUI pinentry branching :test:
Test that with =env-terminal-p= stubbed t, =epa-pinentry-mode= resolves to ='loopback= after =auth-config= loads; with it stubbed nil, the loopback setting is not applied. Use =cl-letf= around =env-terminal-p=; cover normal, boundary (=epa= already loaded), error (=gpg-connect-agent= missing).
-*** TODO [#C] Minibuffer prompt in real terminal Emacs :verify:
+*** TODO [#C] Minibuffer prompt in real terminal Emacs
=emacs -nw=, open an encrypted file or trigger an auth-source decrypt, confirm the passphrase prompt lands in the minibuffer rather than failing on missing pinentry.
-*** TODO [#C] External pinentry still fires in GUI Emacs :verify:
+*** TODO [#C] External pinentry still fires in GUI Emacs
Restart the daemon, open a GUI frame, trigger an encrypted decrypt, confirm =pinentry-dmenu= (or whatever GUI pinentry is configured) still appears.
-*** TODO [#C] Archive the original L3813 task :chore:
+*** TODO [#C] Archive the original L3813 task
After this work lands, mark the original "Finish terminal GPG pinentry configuration" task DONE with a =CLOSED:= stamp and a one-line note pointing at this parent task.
-** TODO [#C] Google Contacts ↔ org-contacts sync investigation :feature:research:
+** TODO [#C] Google Contacts ↔ org-contacts sync investigation :feature:
From the 2026-06-11 brainstorm. Goal: keep [[file:~/sync/org/contacts.org][contacts.org]] (real org-contacts: PROPERTIES drawers, mu4e completion, org-roam links) in sync with Google Contacts. Google side is solid — official People API (OAuth2, incremental syncToken) or CardDAV; no ToS risk. The hard parts are local: (1) identity — entries have no UID, so two-way needs a GOOGLE_ID property per entry plus a one-time fuzzy reconciliation of the two populated datasets (name/email/phone matching); (2) field mapping — space-separated multi-email in one property, free-text body notes, inconsistent phone formats (normalization decision); (3) conflict policy. First decision gates the rest: one-way Google→org read model (simple) vs true two-way. Candidate architectures: vdirsyncer (proven two-way engine w/ Google support; build only the vCard↔org translation, evaluate org-vcard fidelity) vs a direct People API script with sync state in org properties. Output: recommendation doc in docs/design/ naming direction + the normalization/conflict decisions for Craig. Not :solo: — the one-way-vs-two-way call and normalization policy are Craig's.
-** TODO [#C] Google Voice in Emacs — SMS + dialer investigation :feature:research:solo:
+** TODO [#C] Google Voice in Emacs — SMS + dialer investigation :feature:
From the 2026-06-11 messenger-unification brainstorm. Google Voice has no official API; the viable routes ride the Matrix bridge ecosystem's reverse engineering (mautrix-gvoice). Research pass to establish the 2026 state of play: (1) is mautrix-gvoice healthy and what does its auth flow look like now; (2) any better-maintained alternative (CLI/daemon) for the signel-pattern architecture (external daemon + JSON-RPC + thin Emacs chat client); (3) does call initiation (ring-linked-phone-then-connect, Emacs as dialer) survive in the current protocol — two-way audio in Emacs is out of scope (WebRTC); (4) ToS/account-flag risk assessment for Craig's account. Output: a recommendation doc in docs/design/ naming the architecture (signel-pattern daemon vs Matrix bridge + ement.el) or a no-go with reasons. If go, GV becomes a registered backend under the messenger-unification convention (see the [#B] task below).
-** TODO [#C] latexmk workflow never activates (two breaks) :bug:solo:
+** TODO [#C] latexmk workflow never activates (two breaks) :bug:quick:solo:
=modules/latex-config.el:66= — =:hook (TeX-mode-hook . ...)= gets use-package's =-hook= suffix appended (unbound symbol not ending in =-mode=), registering on nonexistent =TeX-mode-hook-hook=, so =TeX-command-default "latexmk"= is never set. Independently =:80= auctex-latexmk is =:defer t= with no trigger, so =auctex-latexmk-setup= never runs and "latexmk" isn't in TeX-command-list. Fix hook name to =TeX-mode=; change auctex-latexmk to =:after tex=. From the 2026-06 config audit.
-** TODO Manual testing and validation :verify:theme-studio:
+** TODO Manual testing and validation
Exercised once the phases above land.
*** 2026-06-11 Thu @ 18:29:39 -0500 Verified UI-face preview and contrast survive a ground bg change
Craig walked the repro: mode-line with its own fg/bg kept its preview bg and ratio through a ground change; ground-dependent rows re-rated; package-faces contrast column updated. Pass. Closed the [#A] contrast-cell and [#B] preview-bg parents.
@@ -4433,22 +4433,22 @@ What we're verifying: lowering a family's count leaves a referencing face visibl
- Lower that family's count to 2 so blue+3 disappears
- Read the assignment's dropdown
Expected: the dropdown shows "(gone)" for the removed step, never a silent jump to a different color; re-pointing it is a deliberate choice.
-** TODO [#D] theme-studio per-tier reseed controls :feature:theme-studio:
+** TODO [#D] theme-studio per-tier reseed controls :feature:
Deferred from the seeding-engine spec (vNext). V1 reseeds all three guide-owned tiers at once; later consider separate "reseed syntax", "reseed UI", and "reseed package/org" controls if all-at-once proves too blunt. Spec: [[file:docs/design/theme-studio-seeding-engine-spec.org][spec]] (vNext; review folded in 2026-06-08).
-** TODO [#D] theme-studio low-contrast preset/mask mode :feature:theme-studio:
+** TODO [#D] theme-studio low-contrast preset/mask mode :feature:
Deferred from the perceptual color metrics spec (vNext). After raw OKLCH/APCA/DeltaE readouts exist, decide whether to add a named low-contrast workflow: APCA Lc bands, a contrast ceiling/floor mask, or a "soft" sibling to the existing any/AA+/AAA picker mask. Spec: [[file:docs/design/theme-studio-perceptual-color-metrics-spec.org][spec]] (vNext candidates; review folded in 2026-06-08).
-** TODO [#D] theme-studio CIEDE2000 DeltaE option :feature:theme-studio:
+** TODO [#D] theme-studio CIEDE2000 DeltaE option :feature:
Deferred from the perceptual color metrics spec (vNext). v1 uses DeltaE-OK on its native scale with a 0.02 threshold (decided); revisit CIEDE2000 only if the native OKLab scale proves too unfamiliar or poorly calibrated for palette distinguishability. Spec: [[file:docs/design/theme-studio-perceptual-color-metrics-spec.org][spec]] (vNext candidates; review folded in 2026-06-08).
-** TODO [#D] Treesitter grammar offline cache :feature:offline:localrepo:
+** 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=.
-** TODO [#D] Native-comp .eln cache strategy :feature:offline:localrepo:
+** TODO [#D] Native-comp .eln cache strategy :feature:
The native-comp =.eln= cache is Emacs-version-specific; an Emacs upgrade invalidates everything. Document the cache location, what an upgrade triggers, and whether a warm-the-cache script is worth shipping. Cross-linked from =docs/design/localrepo.org=.
-** TODO [#D] System-tool dependency install script :feature:offline:localrepo:
+** TODO [#D] System-tool dependency install script :feature:
=ripgrep=, =fd=, =pandoc=, =prettier=, =pyright=, and other binaries that =cj/executable-find-or-warn= flags at module load are not in =package.el='s reach. Document the required-tool set and ship a setup script (or =pacman=/=apt= invocation set). Cross-linked from =docs/design/localrepo.org=.
-** TODO [#D] Localrepo refresh / update script :feature:offline:localrepo:
+** TODO [#D] Localrepo refresh / update script :feature:
No dedicated update path today — refreshing a pinned package means ad-hoc =cp= from the local elpa mirrors. Document the current shape and decide whether a =scripts/refresh-localrepo.sh= is worth writing. Cross-linked from =docs/design/localrepo.org=.
** TODO [#D] Dashboard over-scroll: pin last line to window bottom :bug:
@@ -4487,7 +4487,7 @@ Three small reveal.js improvements; collected into one task because each on its
2. *Default font sizing for slide elements.* Configure reveal.js font sizes for headings, body text, code blocks, etc. — better defaults via =org-reveal-head-preamble= CSS or a custom theme.
3. *Custom dupre reveal.js theme.* CSS theme using the colors from =themes/dupre-palette.el=. Install into =reveal.js/css/theme/= for use with =#+REVEAL_THEME: dupre=.
-** TODO [#B] "? = curated help menu" convention across modes :feature:ux:discoverability:
+** TODO [#B] "? = curated help menu" convention across modes :feature:
From the calibredb keybindings work 2026-06-06. The pattern that worked: in a modal/major-mode buffer (calibredb), bind =?= to a curated transient of the frequent workflows, and move the package's own full dispatch to =H=. It fixes the "I can't discover the keys" problem that which-key can't help with (which-key only pops up after a prefix, not for top-level single keys in a mode-map).
Task: survey the modes/modules Craig works in and identify where a =?= -> curated-help-menu (transient) makes sense. Candidates: any major-mode buffer with single-key bindings and no good discovery affordance -- calibredb (done), nov, dirvish, mu4e, ghostel/term, signel, pearl/linear, ELFeed, etc. For each, note whether =?= is free or already a help dispatch, and whether a curated menu (vs the package's own) adds value. Establish it as a convention (and maybe a small helper/macro to define a curated =?= menu consistently).
@@ -4495,12 +4495,12 @@ Task: survey the modes/modules Craig works in and identify where a =?= -> curate
** TODO [#C] the preview splits an already split window into 3 temporarily.
looks strange. potentially problematic for ai-terms.
-** DOING [#C] Project-aware bug capture via C-c c t :feature:capture:
+** DOING [#C] Project-aware bug capture via C-c c t :feature:
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. Awaiting Craig's interactive manual verify (see the Manual Testing task) before close. NOTE: the matching "<Project> Resolved Work" header for the wrap-up workflow is a separate concern, not handled here.
-** VERIFY [#C] Palette-columns spec review :theme-studio:
+** VERIFY [#C] Palette-columns spec review
SCHEDULED: <2026-06-12 Fri>
Read [[file:docs/theme-studio-palette-columns-spec.org][docs/theme-studio-palette-columns-spec.org]] (Draft, from the 2026-06-10 design discussion) and bless or amend. Decisions 9 and 10 are the two session calls awaiting your word: strips flip to lightest→darkest top→bottom to match the dropdown, and each dropdown column run places the base at its natural lightness position (vs bg/fg bases leading before any steps). On "spec's good": mark Ready, file the phase breakdown, cancel the [#C] hint-override task, start Phase 1.