aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-06-12 07:19:09 -0500
committerCraig Jennings <c@cjennings.net>2026-06-12 07:19:09 -0500
commit71d1bef41d26bc2fee9166785b95946ca80f7e5f (patch)
tree0e0e95f4d8a59871ec221bb7805cf6573750dbd4
parentec81a426c6bb40638cd4c738751208a69685d84b (diff)
downloaddotemacs-71d1bef41d26bc2fee9166785b95946ca80f7e5f.tar.gz
dotemacs-71d1bef41d26bc2fee9166785b95946ca80f7e5f.zip
chore(todo): restrict tags to six, audit solo/quick, close finished tasks
Document the six allowed task tags (bug, feature, refactor, test, quick, solo) in the priority scheme and strip every other tag from Open Work, normalizing tests to test. Re-audit solo and quick against the rubric. Close three stale parents whose children all landed (org cache lifecycle, shell-command audit, UI/navigation hardening) and drop a duplicate ghostel Phase 2 task already logged as done.
-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.