diff options
Diffstat (limited to 'todo.org')
| -rw-r--r-- | todo.org | 554 |
1 files changed, 396 insertions, 158 deletions
@@ -41,15 +41,192 @@ 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 [#C] Pearl vanilla dogfooding follow-ups :pearl:cleanup: +** TODO [#C] Evaluate jamescherti essential-emacs-packages list :packages:research: +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. + +** DONE [#B] theme-studio comprehensive previews (org/magit/elfeed/ghostel/mu4e/dashboard) :feature:theme:theme-studio: +CLOSED: [2026-06-08 Mon] +Expanded the bespoke previews to near-complete face coverage and added three new ones. org now exercises 83/88 faces (document + agenda; the 5 skipped are non-visual: org-hide, org-indent, org-clock-overlay, org-default, org-date-selected). magit 97/98 (status buffer + blame/reflog/sequence/bisect/signature sampler rows). elfeed 13/13. New bespoke previews: ghostel 19/19 (mock terminal, 16 ANSI colors + default + fake cursor), mu4e 37/37 (curated face list, not in the generated inventory; headers list + message view + compose), dashboard 8/8. So clicking a face row flashes a real preview element for nearly every face. Originally filed as just the org preview. + +** DONE [#A] theme-studio theme.json -> dupre-*.el converter :feature:theme:theme-studio: +CLOSED: [2026-06-08 Mon] +Built as scripts/theme-studio/build-theme.el (sibling to build-inventory.el), emitting a single self-contained themes/<name>-theme.el deftheme (not the palette/faces/theme trio — a theme.json carries resolved per-face hex, not dupre's semantic layer). All four tiers convert: default from assignments.bg/.p, syntax categories -> font-lock/tree-sitter faces with bold/italic sets, UI passthrough, packages with :inherit/:height/weight/slant. 20 ERT tests in tests/test-build-theme.el (Normal/Boundary/Error + an end-to-end load + a WCAG-AA assertion on the round-tripped result). One mapping limitation documented: the dec (decorator) key has no independent Emacs face (Emacs renders decorators with font-lock-type-face, which ty owns), so dec is omitted and decorators follow the type color. + +The last link in the pipeline: turn a theme.json exported by the theme-studio into a real loadable Emacs theme. Elisp (per Craig), TDD — this is the correctness-sensitive piece. + +Inputs (all on disk; no chat history needed): +- theme.json contract: =scripts/theme-studio/README.md= (theme.json section) and =docs/design/theme-studio-package-faces-spec.org= (State and export policy, Relative height, Inheritance). +- Reference face layout: existing =themes/dupre-palette.el= + =themes/dupre-faces.el= + =themes/dupre-theme.el=, and =tests/test-dupre-theme.el= (WCAG-contrast helper to reuse). +- Conventions: =.claude/rules/elisp.md=, =.claude/rules/elisp-testing.md=. + +Scope: +1. Read theme.json. Set =default= from =assignments.bg= / =assignments.p=. +2. Author the syntax category -> font-lock face map (~21 keys: kw->font-lock-keyword-face, str->font-lock-string-face, fnd->font-lock-function-name-face, fnc->font-lock-function-call-face, op->font-lock-operator-face, punc->font-lock-punctuation-face, etc. incl. the Emacs-29 tree-sitter additions). Apply =bold= / =italic= sets. +3. UI faces: the =ui= keys are already real face names (region, cursor, mode-line, ...) -> near 1:1 passthrough of fg/bg. +4. Package faces: =packages= -> each face spec, writing =:inherit PARENT= for inherited faces + only the overridden attrs, =:height= when != 1.0, weight/slant. +5. Emit a deftheme file (or palette+faces+theme trio mirroring dupre's layout). + +TDD targets: old-JSON (no packages) loads; every category maps; round-trip of fg/bg/bold/italic/inherit/height into valid face specs; WCAG-contrast assertion on the result. Decide whether the converter lives under =scripts/theme-studio/= (emits to =themes/=) or =themes/=. + +** TODO [#B] Dupre diff-changed / diff-refine-changed legibility :bug:dupre: +Surfaced 2026-06-07 from a pearl session designing its modified-ticket indicator (pearl marks a changed field by inheriting =diff-changed=). dupre's =diff-refine-changed= is bright gold (#ffd700) under near-white text (#f0fef0) -- WCAG contrast ~1.35, unreadable as a plain background. It only looks fine inside diff-mode because diff-mode overlays its own dark foreground. =diff-changed= (#875f00 amber) is ~5.49, readable but off the modus model. Every modus variant keeps both faces legible (contrast 9-16) by pairing a dark low-saturation background with a hue-matched foreground. + +Ask: +1. Rework dupre's =diff-changed= and =diff-refine-changed= on modus lines: dark low-saturation background, legible foreground (plain default fg for simplicity, or hue-tinted per modus -- decide), and keep refine slightly stronger than changed (refine is the word-level emphasis inside a changed region; modus keeps them distinct). +2. While there, audit dupre's broader diff/palette faces against modus conventions (background/foreground tinting, contrast targets) and flag where it diverges. + +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: +A full =make test= run (2026-06-07) is green across 516 of 517 files; the only failures are 4 tests in =tests/test-dupre-theme.el=, long pre-existing. Two root causes. For each, decide whether the palette or the test assertion is canonical, then fix the loser so =make test= goes fully green. + +*** TODO Background drift: 3 tests expect #151311, palette bg is #0d0b0a +=dupre-get-color-base= (test:46), =dupre-theme-default-face= (test:84), and =dupre-with-colors-binds-values= (test:62) all assert the default background is "#151311", but =themes/dupre-palette.el= defines =bg= as "#0d0b0a". The committed palette looks intentional, so the three assertions are likely just stale -- confirm #0d0b0a is the wanted background, then update the tests. + +*** 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 [#C] dupre-clear theme — contrast-first AAA sibling :feature:theme:dupre: +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). +** DONE [#B] theme-studio tier-3 package faces :feature:theme:theme-studio: +CLOSED: [2026-06-08 Mon] +Package-specific face editing in the theme-studio: org/magit/elfeed bespoke (complete face tables + live previews) plus a generated all-package inventory so every installed package is themeable. Spec is Ready, all opens resolved: [[file:docs/design/theme-studio-package-faces-spec.org][docs/design/theme-studio-package-faces-spec.org]]. Phases below run in dependency order; phases 1-5 deliver the three high-value apps, phase 6 opens the long tail, phase 7 documents. The =theme.json= -> =dupre-*.el= converter (Elisp) is a separate downstream task. + +*** 2026-06-08 Mon @ 00:17:41 -0500 Phase 1 — package state + schema landed +Added =APPS= (org starter) and =PKGMAP= ({app:{face:{fg,bg,bold,italic,inherit,height,source}}}), pure helpers (=seedPkgmap= / =packagesForExport= / =mergePackagesInto=), and wired export/import for the =packages= key with old-JSON compat. The =height= float (relative size, read off the face not cascaded through inherit) and the fixed-pitch inherits are seeded in the org starter. No UI yet (Phase 3). Verified: node-check, plus a guarded =#selftest= harness (headless Chrome) confirming seed->export->import round-trip, old-JSON merge, and inherit/height/source survival — all PASS. + +*** 2026-06-08 Mon @ 02:16:24 -0500 Phase 2 — curated app data (org/magit/elfeed) landed +Filled =APPS= with the complete own-defface sets built from embedded face-name lists + a curated seed-color map: org 88 (85 seeded, incl. org-agenda, heading heights, fixed-pitch inherits), magit 98 (64 seeded), elfeed 13 (all seeded). Long-tail faces seed to default fg. Verified: 199 faces total, no seed typos / no dupes, schema self-test PASS seeding all of them. Seeded-default aesthetics still go to Manual testing once the Phase 3 UI lands. + +*** 2026-06-08 Mon @ 02:23:56 -0500 Phase 3 — package face table UI landed +Added the "package faces" section: app selector (org/magit/elfeed), per-app face table with fg/bg dropdowns, bold/italic toggles, inherit dropdown (base faces + the app's own faces), relative-height stepper, live contrast readout on the effective (inherit-resolved) color, per-face and per-app reset, and a text filter. Refactored the fg/bg dropdown into a shared =colorDropdown= helper the ui-faces table now also uses (no =uiSelect= fork). Palette edits propagate to package faces; import/export carry them. Right pane is the generic preview (face names in their own resolved colors) until the bespoke org/magit/elfeed previews land (phases 4-5). Verified: node, headless screenshot, schema self-test PASS. + +*** 2026-06-08 Mon @ 02:27:51 -0500 Phase 4 — org preview landed +Added =renderOrgPreview()=: a mock org document painted live from the org package faces (title, headings with heights, TODO/DONE, tag, scheduled date, property drawer, inline code/verbatim, link, checkbox, quote, src block, header-row table). The preview pane dispatches on the app's preview key; org-mode gets this, others keep the generic list. Verified: node, headless screenshot, self-test PASS. + +*** 2026-06-08 Mon @ 02:30:42 -0500 Phase 5 — magit + elfeed previews landed +Bespoke =renderMagitPreview()= (status buffer: head/branches, untracked, a diff hunk with context/added/removed, recent commits with hashes/authors/keyword/tag) and =renderElfeedPreview()= (search list: filter, dated entries with feed/unread-title/read-title/tags, log lines by level). The preview label now names the app and notes generic vs bespoke. Verified: node, headless screenshots, self-test PASS. + +*** 2026-06-08 Mon @ 02:32:44 -0500 Phase 6 — generated all-package inventory landed +=build-inventory.el= (loaded into a running Emacs) groups every installed package's faces by the defining package and writes =package-inventory.json=. =generate.py= embeds it and merges each package into the dropdown as an editable generic app, leaving org/magit/elfeed bespoke. 40 apps now (3 bespoke + 37 inventory, 643 faces). Committed data artifact, refreshed by reloading the .el; never browser-side discovery. Verified: node, self-test PASS, app count + bespoke-preserved checks. + +*** 2026-06-08 Mon @ 02:34:01 -0500 Phase 7 — docs landed +Rewrote =README.md= for the full tool: three face tiers + palette, the in-page picker (with the AA/AAA mask), package faces (bespoke vs generic previews), modeled inheritance + relative height (family stays in font-config.el), the packages schema with inherit/height/source, export-vs-save, and the inventory-refresh command (=build-inventory.el=) + its loaded-config dependency. Notes =theme-studio.html= is generated. Test-surface fixtures tracked separately below. + +*** 2026-06-08 Mon @ 02:40:00 -0500 theme-studio tier 3 — test surface landed +Extended the guarded =#selftest= harness (headless Chrome) to assert the acceptance criteria against the real emitted code: old-JSON import (no =packages=), full round-trip (fg/bg/bold/italic/inherit/height/source), cleared-state export, unknown-package/face preservation, and inheritance-cycle termination — all PASS. The two DOM-coupled regressions are handled structurally: =updateColor= remaps =PKGMAP= on a palette-color edit, and =PKGMAP= stores hexes so a deleted palette color leaves package refs in the "(gone)" recoverable state. =generate.py= rebuilds =theme-studio.html= each run. + +** TODO [#B] theme-studio perceptual color metrics :feature:theme:theme-studio: +Spec (Ready, opens confirmed 2026-06-08): [[file:docs/design/theme-studio-perceptual-color-metrics-spec.org][docs/design/theme-studio-perceptual-color-metrics-spec.org]]. OKLCH model + perceptual-L/APCA readouts + pairwise ΔE, for building low-contrast themes by metric rather than by eye. Phases run in dependency order; the math core extracts to a Node-unit-tested =colormath.js= and the browser hash tests verify UI wiring. vNext deferrals (low-contrast preset, CIEDE2000) are the two [#D] tasks below. +*** 2026-06-08 Mon @ 19:43:50 -0500 Color-math foundation + Node tests landed +Pure color core in =scripts/theme-studio/colormath.js= (OKLab/OKLCH, APCA-W3 0.1.9 exact constants, ΔE-OK, binary-search gamut clamp returning ={hex,clamped}=) shipped in 49342bf5; this phase finished the integration in 78260018. =generate.py= now inlines the colormath.js body into the page script (export-stripped, =COLORMATH_J= placeholder), and the page's lin/rl/contrast/rating/hsv2rgb/rgb2hsv/hex2rgb/rgb2hex copies moved into the module — =rl= reuses the canonical =lin= (0.04045 cutoff), byte-identical to the old 0.03928 form on every #rrggbb (no 8-bit channel falls between the cutoffs; verified over 200k pairs, zero contrast change). =test-colormath.mjs= gained Normal/Boundary/Error cases for the migrated helpers, a seeded hsv-rgb round-trip property test, and an inline-integrity check that the generated page carries the module body verbatim. Gate met: =node --test scripts/theme-studio/*.mjs= 15 pass, colormath.js 100% line / 93.75% branch / 100% func; =node --check= on the spliced script clean; =#selftest= + =#cursortest= PASS in headless Chrome. NOTE: =node --test <dir>= directory-globbing is broken on Node v26 (tries to load the dir as a module) — use the =*.mjs= glob form. +*** 2026-06-08 Mon @ 19:55:53 -0500 Picker OKLCH/APCA readouts landed +Phase 2 shipped in 77c7f126. Second readout row (=.pinfo2=) under the WCAG ratio: OKLCH L/C/H + signed APCA Lc against the ground color, always shown; sign convention in the APCA tooltip + README. Tables unchanged (APCA picker-only per Agreed-decision #3). =pkReadout= drives the spans from the inlined colormath functions. Gate met: =#readouttest= asserts the spans match the live computation AND the known dupre-blue OKLCH reference (L 0.591 / C 0.052 / H 252°, APCA Lc -34 on ground) with WCAG unchanged; =#selftest= + =#cursortest= still PASS; 15 Node tests green. Headless-rendered values verified against a node cross-check. Visual eyeball is the open "Perceptual readouts read well in the picker" item under Manual testing. +*** TODO [#B] Palette ΔE warnings :solo: +Phase 3. Pairwise ΔE-OK across =PALETTE=; warn on pairs below 0.02 (named constant), sorted closest-first, capped at 5 with "and N more"; nearest-neighbor ΔE in each chip title. Gate: =#deltatest= PASS. +*** TODO [#B] OKLCH sliders + color-model control :solo: +Phase 4a. Separate =pkModel= (HSV/OKLCH) from =pkMode= (AA/AAA mask); OKLCH L/C/H numeric + range inputs driving =oklch2hex=; clamp status text. HSV stays default. Gate: =#oklchtest= (model/mask independence + color preserved across switch) PASS. +*** TODO [#B] Chroma×Lightness plane :solo: +Phase 4b. Render the SV box as a C×L plane at fixed hue in OKLCH mode; gamut-mask the out-of-range region (reuse the =drawMask()= pattern); cache on (hue + dims + mask + bg hex); defer redraw until pointer settles. Gate: the 4b plane hash test (crosshair lands at the color's C/L; a known out-of-gamut coordinate is masked). +*** TODO [#B] Test surface green :solo:tests: +Keep the Node unit tests (+coverage), the UI hash tests (=#cursortest=/=#readouttest=/=#deltatest=/=#oklchtest=/4b), =#selftest=, the inline-integrity check, =node --check=, and the README/doc updates green across the feature. +** TODO Manual testing and validation :verify:theme-studio: +Exercised once the phases above land. +*** TODO Seeded package-face defaults look right +What we're verifying: the seeded org/magit/elfeed default colors read well before any tuning. +- Open =scripts/theme-studio/theme-studio.html= in Chrome +- Switch the app selector to org-mode, then magit, then elfeed +- Read each preview pane against the dupre ground +Expected: each package's seeded defaults look coherent and legible; nothing is unreadable or jarring. Override anything off in the tool. +*** TODO Large face tables stay usable +What we're verifying: org's ~88-face and magit's ~111-face tables stay navigable. +- Select org-mode and scroll the grouped face table +- Type "agenda" in the filter +- Reassign one face and watch the preview +Expected: rows are grouped, the filter narrows them, and a reassignment updates the preview live. +*** TODO Perceptual readouts read well in the picker +What we're verifying: the OKLCH L/C/H and APCA Lc readouts are legible and correctly placed beside the WCAG number. +- Open =scripts/theme-studio/theme-studio.html= in Chrome and open the picker on a few colors +- Read the OKLCH and APCA values against the WCAG ratio +Expected: the new readouts are clear, the APCA sign/polarity is understandable, and nothing crowds the readout bar. +*** TODO ΔE warnings read clearly +What we're verifying: the too-similar-pair warning is legible and the cap behaves. +- Build a palette with two near-identical colors, then a well-spread one +- Read the warning line each time +Expected: the close pair is named with its ΔE, sorted closest-first, capped at 5 with "and N more"; the well-spread palette shows no warning. +*** TODO OKLCH editor feels right +What we're verifying: the OKLCH sliders / C×L plane edit cleanly and clamping is visible. +- Switch the picker to OKLCH mode and drag L, then C, then H +- Push chroma past the sRGB gamut, then toggle the AA/AAA mask +Expected: each axis moves independently; the C×L plane (once 4b lands) opens on the current color; "chroma clamped to sRGB" shows on clamp; toggling the mask does not reset OKLCH mode. +** TODO [#B] theme-studio guide-support features :feature:theme-studio: +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: +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: +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: +Keep #seedtest, #selftest, the default-on-open check, the dupre-revised round-trip, node --check, and Chrome validation green. +** TODO [#D] theme-studio per-tier reseed controls :feature:theme-studio: +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: +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: +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 [#B] Dashboard keybinding changes :quick: :PROPERTIES: -:LAST_REVIEWED: 2026-06-05 +:LAST_REVIEWED: 2026-06-06 :END: -From the pearl-session handoff (2026-06-02) after =modules/linear-config.el= was reduced to a vanilla pearl setup (commit 09b3b13). Open items to revisit after dogfooding pearl's out-of-box config: +pressing g has should refresh. find another binding for Telegram. +** TODO [#A] Calibre Open Work :calibre: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-06 +:END: +Parent grouping the open Calibre / ebook-workflow issues; close each child independently. The EPUB reading-width tasks were already resolved (2026-05-12/14). + +*** DOING Calibre bookmark title format :feature:solo:quick: +When I hit m in calibre, I'm making my place in the book with a bookmark. +While sometimes, the books look fine: "The A.B.C. Murders - Agatha Christie.epub" +Sometimes they look not so good: Engines of Logic_ Mathematicians and the O - Martin Davis.pdf or Software Architecture_ The Hard Parts _ Mo - Neal Ford.pdf -- Decide whether to restore any conveniences dropped in the vanilla rework: default team (DeepSat SE id, in a commented =:custom= block in linear-config.el), the custom keymap, the lazy-key advice. Weigh the eager-:config key read (fires the GPG prompt on first pearl command vs never-at-startup). -- Confirm linear-config.el's module-header fields (Layer / Runtime requires / Direct test load) pass any module-doc linter this repo runs. -- custom-file interaction: =system-defaults.el= redirects =custom-file= to a throwaway temp, so pearl's =customize-save-variable= persistence (default view/team) holds for the session but vanishes on restart. Set those via init-level =:custom= instead. (A pearl-side task was filed to harden pearl's persistence against a disabled custom-file.) +What I would like to do is to have the bookmarks be saved in the following format: + +Author, Title [no extension]. Underscores should be stripped. + +Root cause: in a nov buffer =m= is =bookmark-set= (rebound at calibredb-epub-config.el:311); nov's =nov-bookmark-make-record= names the record =(buffer-name)= -- the EPUB filename. + +Implemented 2026-06-06. Source decision: parse the *filename*, not the embedded EPUB metadata -- under Calibre's "<Title> - <Author>.epub" naming the filename is more complete (the embedded metadata had truncated titles, author-sort "Last, First" forms, and lost punctuation; see the separate metadata-cleanup task). A =:filter-return= advice on =nov-bookmark-make-record= rebuilds the name from the record's filename: split on the last " - " into title/author, restore the colon Calibre sanitized to "_ " (-> ": "), reorder to "Author, Title". Pure helpers =cj/--nov-clean-title= + =cj/--nov-bookmark-name-from-file= in =modules/calibredb-epub-config.el=; 10 ERT tests in =tests/test-calibredb-epub-config--bookmark-name.el=. Live in the daemon. + +Existing bookmarks: the 3 nov bookmarks in =~/sync/org/emacs_bookmarks= were renamed by hand (one-pass, in the daemon + saved; backup at =emacs_bookmarks.bak-2026-06-06=): "Edward Kanterian, Frege: A Guide for the Perplexed", "Agatha Christie, The A.B.C. Murders", "Edward Abbey, The Fool's Progress: An Honest Novel". + +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: +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: + - by format (e.g. epubs only) + - by author last name (exact == or ^begins-with some text) + - sort by title, publication date, or group by format +- One key pops up the selected book's description in a bottom-30% buffer, dismissed with q (same display pattern as the signel chat dock). +- RET opens the book in the appropriate viewer. +Survey finding 2026-06-06: calibredb already binds almost all of this in calibredb-search-mode-map (S/L library, g filter [f format, a author, t tag, d date], o sort [t title, a author, p pubdate, f format], RET open) and even ships transient menus (? = calibredb-dispatch, g, o). The real problem was discoverability -- they are top-level single keys (which-key never pops up) and Craig didn't know ? opened a menu. calibredb-quick-look is macOS-only; the detail view (v -> *calibredb-entry*, q quits) is the description but opens full-window. + +Implemented 2026-06-06 in =modules/calibredb-epub-config.el=: +- A curated transient =cj/calibredb-menu= (library switch; filter format/author/reset; sort author/title/pubdate/format; open; describe; H = full calibredb-dispatch) bound to =?= in calibredb-search-mode-map. calibredb's own full dispatch moved to =H=. Defined in the use-package =:config= (needs the elpa transient, which batch doesn't load) -- the "? brings up a curated help menu" convention. +- 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: +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 [#B] TTY-accessible personal C-; keymap :feature:ux:solo:quick: :PROPERTIES: @@ -66,9 +243,9 @@ Easy prefix candidates (home-row-leaning, TTY-safe), same leaf keys under each: While in here, audit individual leaf chords for other non-TTY keys (any =C-RET=, super/hyper bindings — terminals can't send super/hyper either) and note or remap them. Verify the result in an actual =emacs -nw= / =emacsclient -nw= frame, not just GUI. Relates to the standing "org-mode keybinding consolidation" reminder. -** DOING [#B] Signal client — forked signel :feature: +** DOING [#B] Signel Client Open Work :PROPERTIES: -:LAST_REVIEWED: 2026-06-05 +:LAST_REVIEWED: 2026-06-06 :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. @@ -102,131 +279,15 @@ Fork commit 5ec56c0 added =signel--pending-input= (capture from input-marker to *** TODO [#D] Include Signal groups in the picker :feature:no-sync: 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. -*** TODO [#B] C-; M prefix binding doesn't take effect on fresh Emacs launch :bug: -Surfaced during the 2026-05-28 manual verify of test 1. After a fresh restart, =C-; M SPC= did nothing — the =cj/signel-prefix-map= wasn't bound under =M= in =cj/custom-keymap=. A live-reload of =modules/signal-config.el= via =emacsclient -e '(load ...)'= immediately activated the binding, so the wiring at =signal-config.el:278-280= (=(with-eval-after-load 'keybindings (when (boundp 'cj/custom-keymap) (keymap-set cj/custom-keymap "M" cj/signel-prefix-map)))=) IS correct in isolation but isn't firing on the real launch sequence. Likely a load-order interaction or a use-package init/config timing issue (cf. the CLAUDE.md gotcha "Run a full Emacs launch after any use-package :config block edit" — modules can byte-compile clean and pass unit tests but fail at full Emacs launch). Probable fix path: switch signal-config.el to the documented =cj/register-prefix-map= helper from =modules/keybindings.el= (the helpers exist exactly to remove each module's hidden assumption that =cj/custom-keymap= is bound at the right moment). Reproduce: restart Emacs and try =C-; M SPC= without first reloading =signal-config.el=. +*** 2026-06-06 Sat @ 12:29:24 -0500 Fixed C-; M load-order bug via canonical register-prefix-map +Root cause: signal-config.el was the only feature module that violated the prefix-registration contract documented in =keybindings.el:41-45=. Every other prefix map uses =(require 'keybindings)= + a top-level =(cj/register-prefix-map "X" map)=; signal-config had neither, mutating =cj/custom-keymap= directly through a =(with-eval-after-load 'keybindings (when (boundp 'cj/custom-keymap) ...))= form. The =boundp= guard turned a load-order miss into a SILENT no-op — no error, the binding just never happened — which is why a live-reload (keybindings definitely loaded by then) papered over it. +Fix: added =(require 'keybindings)= at the top of signal-config.el and replaced the guarded form with =(cj/register-prefix-map "M" cj/signel-prefix-map "signal messages")=, matching the 25+ other prefix maps. +Verified: (1) new contract test =test-signal-config-prefix-map-registered-under-c-semi-m= asserts =C-; M= resolves to =cj/signel-prefix-map= (35/35 green); (2) full =emacs --batch= init.el launch — the exact failing scenario — now shows =C-; M= bound; (3) clean byte-compile; (4) live-reloaded into the daemon, binding confirmed. No unit-level red was possible: the =boundp= guard is robust under all standard test timings, which is the CLAUDE.md launch-only-failure class. *** 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. -** TODO [#B] Emacs Manual Testing and Validation :verify: -SCHEDULED: <2026-05-29 Fri> -:PROPERTIES: -:LAST_REVIEWED: 2026-05-28 -:END: - -Hand-verify checklist Craig walks one item at a time after the relevant code lands. Each child names what is being verified, the exact steps to run, and the observable expected result. On pass, the child gets marked or deleted. On fail, the actual behavior gets logged under the step and the child is promoted to a top-level =TODO= bug per the verification.md handoff rule. - -Walk started 2026-05-28 (tests 1 + 2 verified — surfaced two Signel bugs along the way, both fixed before continuing). Deferred to 2026-05-29: test 3 onward needs sending an actual Signal message, too late at night to be polite about it. Picker → chat buffer opens cleanly; the send half is what remains to exercise. - -*** 2026-05-28 Thu @ 02:13:55 -0500 Verified: connect starts the daemon (after fix) -=C-; M SPC= → "Signel connected." in echo area; =M-x list-processes= shows =signal-rpc= running (PID 1775279, command =/usr/bin/signal-cli -a +1510...=). Two bugs surfaced and fixed during the verify: -- The =with-eval-after-load 'keybindings= binding at =signal-config.el:280= didn't take effect on a fresh Emacs restart; a live-reload of =signal-config.el= activated the =C-; M= prefix. Logged as a separate top-level TODO for follow-up (load-order or use-package interaction). -- =cj/signel--ensure-started= referenced =signel--process-name= before signel had been autoloaded — the bare forward-declared =(defvar signel--process-name)= didn't actually bind the variable. Fix: added =(require 'signel)= at the top of the function (=signal-config.el:170=) so the package loads before any of its private variables are read. New ERT test =test-signal-config-ensure-started-requires-signel= captures the bug. - -*** 2026-05-28 Thu @ 02:16:45 -0500 Verified: picker opens with contact names -=C-; M m= → minibuffer opened within ~1s, "Note to Self" pinned at the top, the 94 Signal contacts followed labeled "Name (+number)". Picker behavior matches spec. Surfaced a follow-up on the chat buffer that opens after a pick — placement + exit keys want refining; filed under L44 Signel. - -*** Signel: pick a contact and send a message -What we're verifying: choosing a contact opens a chat buffer, =RET= at the prompt sends through =signel--send-input=, and the message arrives on the recipient's phone. -- =C-; M m=, pick a contact you trust. -- Type a short message at the prompt, press =RET=. -- Check the recipient's phone. -Expected: a =*Signel: +<number>*= buffer opens, the typed message renders with the =[HH:MM] <Me>= prefix on send, and arrives on the recipient's phone within a few seconds. - -*** Signel: Note-to-Self lands in the right Signal thread -What we're verifying: =cj/signel-message-self= (=C-; M s=) resolves to =signel-account= and sending through it lands in the *Note to Self* thread on the phone, NOT a self-addressed display anomaly. This is the spec's medium-priority manual verify from D3. -- Press =C-; M s=. -- Type "test note to self" at the prompt, press =RET=. -- Open Signal on your phone, scroll to the *Note to Self* thread. -Expected: a =*Signel: +<your-number>*= buffer opens in Emacs, the message sends, and the message appears in the phone's *Note to Self* thread (not in any other conversation). - -*** Signel: Note-to-Self via the picker's pinned entry -What we're verifying: picking the pinned "Note to Self" entry through =cj/signel-message= resolves the same way as the direct command. -- =C-; M m=, choose "Note to Self". -Expected: the same =*Signel: +<your-number>*= buffer opens. (No need to re-send; opening the right buffer proves the resolution.) - -*** Signel: typed input survives an incoming message -What we're verifying: the clobber fix (fork commit 5ec56c0) preserves in-progress prompt input across =signel--insert-msg= when a message arrives mid-typing. -- =C-; M m=, pick a contact. -- Type a long unsent message at the prompt, do NOT press =RET=. -- From a second device or by asking someone, send yourself a Signal message that lands in this chat (or any active chat). -Expected: the incoming message renders above the prompt, the prompt redraws, and your typed text is still there at the prompt ready to send. - -*** Signel: dashboard opens -What we're verifying: =signel-dashboard= (=C-; M d=) opens the active-chats dashboard. -- Press =C-; M d=. -Expected: a dashboard buffer opens listing active chats. - -*** Signel: stop tears down the daemon -What we're verifying: =signel-stop= (=C-; M q=) deletes the process and clears the request-handler / buffer maps (the reconnect-invalidation contract from fork commit 4740d97). -- Press =C-; M q=. -- =M-x list-processes=. -Expected: echo area shows "Signel service stopped.", and =list-processes= no longer lists =signal-rpc=. - -*** Signel: refresh forces a fresh contact fetch -What we're verifying: =cj/signel-refresh-contacts= clears the cache and re-fetches via the new callback contract. -- =C-; M SPC= to reconnect if you ran the stop test above. -- =M-x cj/signel-refresh-contacts=. -- Immediately =C-; M m=. -Expected: the picker still opens cleanly with the same contact list (the refresh is silent; the picker is the visible check). If you added a contact on the phone, it now appears. - -*** Font setup reaches a GUI frame created after a TTY frame (daemon) -What we're verifying: emoji glyphs + fonts apply in a GUI frame even when the first daemon frame was a TTY. -- emacs --daemon -- emacsclient -t (TTY frame first) -- emacsclient -c (then a GUI frame) -- in the GUI frame, open a buffer with an emoji and check it renders, and M-S-f / fonts look right -Expected: emoji renders and fonts are applied in the GUI frame. - -*** ghostel migration: Claude Code TUI in a GUI frame -What we're verifying: an agent runs in ghostel with good rendering (the reason for the engine swap). -- restart Emacs (the migration changes load order + a use-package :config block) -- in a GUI frame press F9, pick a project, let Claude stream a long response (big diff or file read) -Expected: colors look right (not washed out), no flicker/strobing during the stream, box-drawing and the cursor render correctly. - -*** ghostel migration: Claude Code TUI in a TTY frame (replaces the old refuse test) -What we're verifying: D4 dropped the GUI-only guard, so F9 now launches in a terminal frame too. -- emacsclient -t (TTY frame, off the running daemon) -- in the TTY frame press F9 and pick a project -Expected: the agent launches and renders as text + color in the TTY (no echo-area refusal message); inline images are absent, which is expected. - -*** ghostel migration: F9 / C-F9 / M-F9 dispatch -What we're verifying: the agent dispatch behaves as it did on vterm. -- F9 toggles the agent window off/on; C-F9 always opens the project picker; M-F9 closes (kills the tmux session) after confirm -- press F9 from inside an agent buffer (full-frame) — it should toggle, not get swallowed by the terminal -Expected: each chord does its job from both normal and agent buffers. - -*** ghostel migration: tmux integration + C-; x menu -What we're verifying: the tmux machinery ported intact. -- launch an agent; M-x list it — runs in tmux session aiv-<project> -- second F9 on the same project reattaches (no duplicate session) -- C-; x h captures the tmux pane history into an Emacs buffer; C-; x c enters tmux copy-mode -- C-; x l clears scrollback; C-; x n / p navigate prompts -Expected: all menu commands work against the ghostel buffer; history capture + copy-mode behave as before. - -*** ghostel migration: copy-mode parity + mouse wheel -What we're verifying: copy/selection and wheel scrolling survived the engine swap. -- in a ghostel buffer enter copy-mode (C-; x c without tmux, or the tmux path with tmux); M-w copies and stays; q / C-g exit -- mouse-wheel scroll inside tmux, inside Claude Code, and inside lazygit -Expected: M-w copies without leaving; q/C-g exit; the wheel scrolls the program (this replaces the removed vterm wheel-forwarding — confirm ghostel's native SGR mouse covers it). - -*** ghostel migration: other TUIs + ssh -What we're verifying: general terminal workloads render. -- run lazygit, htop/btop, a heavy-output build, and ssh to a remote host in a ghostel terminal (F12) -Expected: each renders and behaves correctly; ssh out works (if a remote lacks xterm-ghostty terminfo, note it — ghostel-ssh-install-terminfo / ghostel-term is the lever). - -*** ghostel migration: F12 general terminal + dashboard launcher -What we're verifying: F12 manages non-agent terminals only, and the dashboard launcher uses ghostel. -- F12 opens/toggles a general terminal; confirm it does NOT grab an agent buffer; resize it, toggle off and on — geometry is preserved -- from the dashboard press t (Terminal) — opens a ghostel terminal (tooltip reads "Launch Terminal") -Expected: F12 excludes agent buffers and keeps saved geometry; the dashboard launches ghostel. - -*** ghostel migration: crash recovery -What we're verifying: the aiv- tmux session survives an Emacs crash and reattaches. -- with a live agent, kill Emacs (not the tmux session); restart Emacs; F9 → project picker -Expected: the project shows "[detached]" and reattaches to the surviving tmux session. - -** DOING [#B] Migrate all terminals from vterm to ghostel :terminal:ghostel: +** DOING [#B] Migrate All Terminals From Vterm to Ghostel :terminal:ghostel: :PROPERTIES: :LAST_REVIEWED: 2026-06-04 :END: @@ -252,16 +313,16 @@ Deleted auto-dim's vterm color-advice + redraw integration (~165 lines; D1 — t *** 2026-06-05 Fri @ 00:50:58 -0500 Phase 4 done: vterm + vterm-toggle removed =package-delete='d vterm + vterm-toggle from elpa. No vterm refs remain in modules/init except intentional historical comments. Suite green except the 4 pre-existing dupre-theme failures (the popper-config one is now fixed). validate-modules ✓; full early-init+init batch smoke = INIT-SMOKE-OK. The migration parent stays DOING until Craig restarts Emacs and walks the ghostel manual-verify matrix under "Emacs Manual Testing and Validation". -*** TODO [#C] Follow-up: theme ghostel ANSI faces in dupre :terminal:ghostel:dupre: +*** TODO [#B] Follow-up: theme ghostel ANSI faces in dupre :terminal:ghostel:dupre: D2 — set the 16 ghostel-color-* + ghostel-default faces in dupre-faces/palette. -*** TODO [#C] Follow-up: evaluate ghostel-eshell + ghostel-compile :terminal:ghostel:eval: +*** TODO [#B] Follow-up: evaluate ghostel-eshell + ghostel-compile :terminal:ghostel:eval: D3 — ghostel-eshell as eshell visual backend; ghostel-compile against F4 dev-fkeys. *** 2026-06-05 Fri @ 14:24:02 -0500 Auto-dim revisit cancelled — current no-dim behavior is fine Craig confirmed the shipped auto-dim setup works fine as-is: terminal buffers don't participate in unfocused-window dimming (D1), and the rest of auto-dim behaves. That is the measured decision the original task asked for — option (a), keep no-dim — so no rework (the focus-loss palette-blend in option (b) or an upstream per-window hook in option (c)) is needed. Closing without further investigation. Context: [[file:docs/design/vterm-to-ghostel-migration-spec.org][migration spec]] D1. -*** TODO [#C] Investigate ghostel selection/highlight color :terminal:ghostel: +*** TODO [#B] Investigate ghostel selection/highlight color :terminal:ghostel: 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-05-26 Tue @ 15:15:43 -0500 Direction confirmed; Claude Code in eat needs a caveat @@ -288,12 +349,6 @@ Folded the external review via spec-response. Craig accepted D1-D5; baked them p *** 2026-06-04 Thu @ 23:30:18 -0500 External re-review: ready Re-reviewed [[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. -** TODO [#C] Slack message buffers in a reused popup window :slack:ux:quick: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-05 -:END: -Display slack.el message and thread buffers in a dedicated popup window (side or bottom) and reuse that one window instead of spawning a new window per buffer. Likely a =display-buffer-alist= rule (or popper integration) in =modules/slack-config.el=. - ** PROJECT [#B] Implement ai-kb :feature:ai:kb: 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. @@ -702,7 +757,7 @@ Update the =dev-fkeys.el= header comment (L33) — TS/JS is no longer punted; th ** TODO [#B] Fix up test runner :bug: :PROPERTIES: -:LAST_REVIEWED: 2026-05-28 +:LAST_REVIEWED: 2026-06-06 :END: *** 2026-05-16 Sat @ 11:15:51 -0500 Ideas **** Current State @@ -2543,7 +2598,23 @@ configuration (=text-config=, =diff-config=, =ledger-config=, =games-config=, =mu4e-org-contacts-setup=, =telega-config=, =httpd-config=, =org-agenda-config-debug=). -** TODO [#C] M-F9 ai-vterm close removes the window split :quick:solo: +** TODO [#B] Add Signal to the dashboard :quick: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-01 +:END: +** TODO [#C] Consider consolidating/harmonizing the UI in all Message Clients +:PROPERTIES: +: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: +:PROPERTIES: +:LAST_REVIEWED: 2026-06-05 +:END: +Display slack.el message and thread buffers in a dedicated popup window (side or bottom) and reuse that one window instead of spawning a new window per buffer. Likely a =display-buffer-alist= rule (or popper integration) in =modules/slack-config.el=. + +** DONE [#C] M-F9 ai-vterm close removes the window split :quick:solo: +CLOSED: [2026-06-06 Sat] :PROPERTIES: :LAST_REVIEWED: 2026-06-02 :END: @@ -2552,6 +2623,9 @@ Closing the ai-vterm with M-F9 while its window is in a split deletes the split *** 2026-06-02 Tue @ 14:12:48 -0500 Audit: still a bug, distinct from the F9 collapse The F9 toggle-off rework (38dad92) made F9 collapse the split by design, but that's the toggle path. This is M-F9 close (kills the agent process): close should leave the surrounding layout intact, not delete the sibling window. Craig confirmed it's still a bug. cj/--ai-vterm-close-buffer still calls delete-window. +*** 2026-06-06 Sat @ 18:18:17 -0500 Fixed: close swaps the window to a non-agent buffer instead of deleting it +=cj/--ai-term-close-buffer= no longer calls =delete-window=; it swaps the agent's window to the working buffer (=cj/--ai-term-most-recent-non-agent-buffer=), then kills the agent buffer, so the split survives. F9 hide still collapses the split by design; close no longer does. Regression test =test-ai-term--close-buffer-keeps-window-split=. Commit =1a097b7e=. + ** TODO [#C] Implement EMMS-free music-config architecture :refactor: :PROPERTIES: :LAST_REVIEWED: 2026-06-01 @@ -2677,7 +2751,7 @@ Depends on: command + Dired/Dirvish rewire. ** TODO [#C] music-config option-combination audit + tests :tests:harden:solo: :PROPERTIES: -:LAST_REVIEWED: 2026-05-28 +:LAST_REVIEWED: 2026-06-06 :END: Two-part task surfaced 2026-05-28 during the Signel verify walk — generalized from the "are there combinations of options that we'd want to disallow together" question. @@ -3853,18 +3927,6 @@ Write the README at the artifact: short prose entry point summarizing the tier m *** TODO [#C] Commentary header in early-init.el :docs: 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 [#D] Treesitter grammar offline cache :feature:offline:localrepo: -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: -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: -=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: -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 [#C] TRAMP/dirvish "?" for remote dates — verify the fix per host :bug: :PROPERTIES: :LAST_REVIEWED: 2026-06-02 @@ -3924,6 +3986,18 @@ Restart the daemon, open a GUI frame, trigger an encrypted decrypt, confirm =pin *** TODO [#C] Archive the original L3813 task :chore: 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 [#D] Treesitter grammar offline cache :feature:offline:localrepo: +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: +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: +=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: +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: :PROPERTIES: :LAST_REVIEWED: 2026-05-22 @@ -3960,10 +4034,16 @@ 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] Add Signal to the dashboard :quick: -:PROPERTIES: -:LAST_REVIEWED: 2026-06-01 -:END: +** DOING Project-aware bug capture via C-c c t :feature:capture: +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. + +** TODO "? = curated help menu" convention across modes :feature:ux:discoverability: +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). + * Emacs Resolved ** DONE [#B] Fix likely =elpa-mirror-location= path bug :bug:quick: CLOSED: [2026-05-03 Sun] @@ -6962,7 +7042,7 @@ Filed 2026-06-02 from a C-f8/C-f9 mix-up. Priority set [#C] (UX polish) — re-g ** TODO [#C] Color dashboard navigator independently of list items :feature:ux: :PROPERTIES: -:LAST_REVIEWED: 2026-05-28 +:LAST_REVIEWED: 2026-06-06 :END: The dashboard navigator (icons + labels) and the recentf/project/bookmark list items are both painted by =dashboard-items-face=: the navigator gets a =dashboard-items-face= overlay, and overlays beat text properties, so the per-button =dashboard-navigator= face is inert. To color the navigator independently of the items, override where that overlay is applied — advise or redefine =dashboard-insert-navigator=, or strip/replace the overlay's face. Triggered by: 2026-05-22 dashboard color work (L105). @@ -6981,3 +7061,161 @@ Changes in progress (modules/auth-config.el): - Use epa-pinentry-mode 'loopback in terminal - Use external pinentry (pinentry-dmenu) in GUI - Requires env-terminal-p from host-environment module +** DONE [#B] Emacs Manual Testing and Validation :verify: +CLOSED: [2026-06-06 Sat 13:59] SCHEDULED: <2026-05-29 Fri> +:PROPERTIES: +:LAST_REVIEWED: 2026-05-28 +:END: + +Hand-verify checklist Craig walks one item at a time after the relevant code lands. Each child names what is being verified, the exact steps to run, and the observable expected result. On pass, the child gets marked or deleted. On fail, the actual behavior gets logged under the step and the child is promoted to a top-level =TODO= bug per the verification.md handoff rule. + +Walk started 2026-05-28 (tests 1 + 2 verified — surfaced two Signel bugs along the way, both fixed before continuing). Deferred to 2026-05-29: test 3 onward needs sending an actual Signal message, too late at night to be polite about it. Picker → chat buffer opens cleanly; the send half is what remains to exercise. + +*** Project-aware capture: C-c c t files into the project's Open Work +What we're verifying: inside a projectile project that has a root todo.org, C-c c t (Task) files the new entry under that project's "<Project> Open Work" heading. +- Open a file inside a projectile project whose root has a todo.org (e.g. this one, ~/.emacs.d). +- Press C-c c, then t. +- Type a short task, finish with C-c C-c. +Expected: the entry lands as a new "** TODO ..." at the top of that project's "... Open Work" heading (e.g. "Emacs Open Work"), not in the global inbox. + +*** Project-aware capture: C-c c b files a [#C] bug +What we're verifying: C-c c b (Bug) behaves like the Task capture but stamps the entry [#C]. +- Inside the same project, press C-c c, then b. +- Type a short bug description, finish with C-c C-c. +Expected: a "** TODO [#C] ..." entry lands at the top of the project's "... Open Work" heading. + +*** Nov bookmark naming: "Author, Title" instead of the raw filename +What we're verifying: bookmarking your place in an EPUB names the bookmark "Author, Title" parsed from the filename (Calibre's "<Title> - <Author>.epub"), reordered with the colon restored — not the raw filename. +- Open an EPUB in nov (m is bound to bookmark-set there). +- Press m to set a bookmark. +- Look at the default name in the bookmark prompt. +Expected: the default is "<Author>, <Title>" (e.g. "Agatha Christie, The A.B.C. Murders"; a colon where the filename had "_ "), no extension, no underscores — not the raw filename. + +*** Calibredb curated menu on ? and full dispatch on H +What we're verifying: in the calibredb buffer, ? opens the curated workflow menu and H opens calibredb's full dispatch. +- M-B to open calibredb. +- Press ?. +- Press a key for a workflow (e.g. o to open, f format filter), or q to quit the menu. +- Press H. +Expected: ? shows the curated transient (Library / Filter / Sort / Book columns with your workflows); the keys run the right calibredb commands; q quits. H shows calibredb's full menu. + +*** Calibredb description docks to the bottom 30% +What we're verifying: viewing a book's description docks it to the bottom 30% and q dismisses it. +- M-B, move to a book. +- Press ? then d (or v). +- Read the description. +- Press q. +Expected: the *calibredb-entry* detail buffer opens docked across the bottom ~30% of the frame (not full-window); q closes it and returns to the list. + +*** Project-aware capture: inbox fallback + warning +What we're verifying: outside a project (or in a project with no todo.org) the capture falls back to the global inbox; the no-todo.org case also warns. +- Open a scratch file not inside any projectile project, C-c c t, type a task, C-c C-c. Expect it under "Inbox" in the global inbox file. +- (If easy) open a file in a projectile project that has NO todo.org, C-c c t. Expect it in the global inbox AND an echo-area message naming the project. + +*** 2026-05-28 Thu @ 02:13:55 -0500 Verified: connect starts the daemon (after fix) +=C-; M SPC= → "Signel connected." in echo area; =M-x list-processes= shows =signal-rpc= running (PID 1775279, command =/usr/bin/signal-cli -a +1510...=). Two bugs surfaced and fixed during the verify: +- The =with-eval-after-load 'keybindings= binding at =signal-config.el:280= didn't take effect on a fresh Emacs restart; a live-reload of =signal-config.el= activated the =C-; M= prefix. Logged as a separate top-level TODO for follow-up (load-order or use-package interaction). +- =cj/signel--ensure-started= referenced =signel--process-name= before signel had been autoloaded — the bare forward-declared =(defvar signel--process-name)= didn't actually bind the variable. Fix: added =(require 'signel)= at the top of the function (=signal-config.el:170=) so the package loads before any of its private variables are read. New ERT test =test-signal-config-ensure-started-requires-signel= captures the bug. + +*** 2026-05-28 Thu @ 02:16:45 -0500 Verified: picker opens with contact names +=C-; M m= → minibuffer opened within ~1s, "Note to Self" pinned at the top, the 94 Signal contacts followed labeled "Name (+number)". Picker behavior matches spec. Surfaced a follow-up on the chat buffer that opens after a pick — placement + exit keys want refining; filed under L44 Signel. + +*** Signel: pick a contact and send a message +What we're verifying: choosing a contact opens a chat buffer, =RET= at the prompt sends through =signel--send-input=, and the message arrives on the recipient's phone. +- =C-; M m=, pick a contact you trust. +- Type a short message at the prompt, press =RET=. +- Check the recipient's phone. +Expected: a =*Signel: +<number>*= buffer opens, the typed message renders with the =[HH:MM] <Me>= prefix on send, and arrives on the recipient's phone within a few seconds. + +*** Signel: Note-to-Self lands in the right Signal thread +What we're verifying: =cj/signel-message-self= (=C-; M s=) resolves to =signel-account= and sending through it lands in the *Note to Self* thread on the phone, NOT a self-addressed display anomaly. This is the spec's medium-priority manual verify from D3. +- Press =C-; M s=. +- Type "test note to self" at the prompt, press =RET=. +- Open Signal on your phone, scroll to the *Note to Self* thread. +Expected: a =*Signel: +<your-number>*= buffer opens in Emacs, the message sends, and the message appears in the phone's *Note to Self* thread (not in any other conversation). + +*** Signel: Note-to-Self via the picker's pinned entry +What we're verifying: picking the pinned "Note to Self" entry through =cj/signel-message= resolves the same way as the direct command. +- =C-; M m=, choose "Note to Self". +Expected: the same =*Signel: +<your-number>*= buffer opens. (No need to re-send; opening the right buffer proves the resolution.) + +*** Signel: typed input survives an incoming message +What we're verifying: the clobber fix (fork commit 5ec56c0) preserves in-progress prompt input across =signel--insert-msg= when a message arrives mid-typing. +- =C-; M m=, pick a contact. +- Type a long unsent message at the prompt, do NOT press =RET=. +- From a second device or by asking someone, send yourself a Signal message that lands in this chat (or any active chat). +Expected: the incoming message renders above the prompt, the prompt redraws, and your typed text is still there at the prompt ready to send. + +*** Signel: dashboard opens +What we're verifying: =signel-dashboard= (=C-; M d=) opens the active-chats dashboard. +- Press =C-; M d=. +Expected: a dashboard buffer opens listing active chats. + +*** Signel: stop tears down the daemon +What we're verifying: =signel-stop= (=C-; M q=) deletes the process and clears the request-handler / buffer maps (the reconnect-invalidation contract from fork commit 4740d97). +- Press =C-; M q=. +- =M-x list-processes=. +Expected: echo area shows "Signel service stopped.", and =list-processes= no longer lists =signal-rpc=. + +*** Signel: refresh forces a fresh contact fetch +What we're verifying: =cj/signel-refresh-contacts= clears the cache and re-fetches via the new callback contract. +- =C-; M SPC= to reconnect if you ran the stop test above. +- =M-x cj/signel-refresh-contacts=. +- Immediately =C-; M m=. +Expected: the picker still opens cleanly with the same contact list (the refresh is silent; the picker is the visible check). If you added a contact on the phone, it now appears. + +*** Font setup reaches a GUI frame created after a TTY frame (daemon) +What we're verifying: emoji glyphs + fonts apply in a GUI frame even when the first daemon frame was a TTY. +- emacs --daemon +- emacsclient -t (TTY frame first) +- emacsclient -c (then a GUI frame) +- in the GUI frame, open a buffer with an emoji and check it renders, and M-S-f / fonts look right +Expected: emoji renders and fonts are applied in the GUI frame. + +*** ghostel migration: Claude Code TUI in a GUI frame +What we're verifying: an agent runs in ghostel with good rendering (the reason for the engine swap). +- restart Emacs (the migration changes load order + a use-package :config block) +- in a GUI frame press F9, pick a project, let Claude stream a long response (big diff or file read) +Expected: colors look right (not washed out), no flicker/strobing during the stream, box-drawing and the cursor render correctly. + +*** ghostel migration: Claude Code TUI in a TTY frame (replaces the old refuse test) +What we're verifying: D4 dropped the GUI-only guard, so F9 now launches in a terminal frame too. +- emacsclient -t (TTY frame, off the running daemon) +- in the TTY frame press F9 and pick a project +Expected: the agent launches and renders as text + color in the TTY (no echo-area refusal message); inline images are absent, which is expected. + +*** ghostel migration: F9 / C-F9 / M-F9 dispatch +What we're verifying: the agent dispatch behaves as it did on vterm. +- F9 toggles the agent window off/on; C-F9 always opens the project picker; M-F9 closes (kills the tmux session) after confirm +- press F9 from inside an agent buffer (full-frame) — it should toggle, not get swallowed by the terminal +Expected: each chord does its job from both normal and agent buffers. + +*** ghostel migration: tmux integration + C-; x menu +What we're verifying: the tmux machinery ported intact. +- launch an agent; M-x list it — runs in tmux session aiv-<project> +- second F9 on the same project reattaches (no duplicate session) +- C-; x h captures the tmux pane history into an Emacs buffer; C-; x c enters tmux copy-mode +- C-; x l clears scrollback; C-; x n / p navigate prompts +Expected: all menu commands work against the ghostel buffer; history capture + copy-mode behave as before. + +*** ghostel migration: copy-mode parity + mouse wheel +What we're verifying: copy/selection and wheel scrolling survived the engine swap. +- in a ghostel buffer enter copy-mode (C-; x c without tmux, or the tmux path with tmux); M-w copies and stays; q / C-g exit +- mouse-wheel scroll inside tmux, inside Claude Code, and inside lazygit +Expected: M-w copies without leaving; q/C-g exit; the wheel scrolls the program (this replaces the removed vterm wheel-forwarding — confirm ghostel's native SGR mouse covers it). + +*** ghostel migration: other TUIs + ssh +What we're verifying: general terminal workloads render. +- run lazygit, htop/btop, a heavy-output build, and ssh to a remote host in a ghostel terminal (F12) +Expected: each renders and behaves correctly; ssh out works (if a remote lacks xterm-ghostty terminfo, note it — ghostel-ssh-install-terminfo / ghostel-term is the lever). + +*** ghostel migration: F12 general terminal + dashboard launcher +What we're verifying: F12 manages non-agent terminals only, and the dashboard launcher uses ghostel. +- F12 opens/toggles a general terminal; confirm it does NOT grab an agent buffer; resize it, toggle off and on — geometry is preserved +- from the dashboard press t (Terminal) — opens a ghostel terminal (tooltip reads "Launch Terminal") +Expected: F12 excludes agent buffers and keeps saved geometry; the dashboard launches ghostel. + +*** ghostel migration: crash recovery +What we're verifying: the aiv- tmux session survives an Emacs crash and reattaches. +- with a live agent, kill Emacs (not the tmux session); restart Emacs; F9 → project picker +Expected: the project shows "[detached]" and reattaches to the surviving tmux session. |
