aboutsummaryrefslogtreecommitdiff
path: root/docs/specs
diff options
context:
space:
mode:
Diffstat (limited to 'docs/specs')
-rw-r--r--docs/specs/2026-07-06-fancy-music-player-ui-spec.org5
-rw-r--r--docs/specs/2026-07-06-radio-browser-lookup-spec.org6
-rw-r--r--docs/specs/2026-07-10-org-workflow-doctor-spec.org260
-rw-r--r--docs/specs/gloss-spec.org323
-rw-r--r--docs/specs/init-load-graph-spec.org34
-rw-r--r--docs/specs/messenger-unification-spec.org1
-rw-r--r--docs/specs/signal-client-spec.org4
7 files changed, 291 insertions, 342 deletions
diff --git a/docs/specs/2026-07-06-fancy-music-player-ui-spec.org b/docs/specs/2026-07-06-fancy-music-player-ui-spec.org
index b3b4c614..44fd8a00 100644
--- a/docs/specs/2026-07-06-fancy-music-player-ui-spec.org
+++ b/docs/specs/2026-07-06-fancy-music-player-ui-spec.org
@@ -4,10 +4,11 @@
#+TODO: TODO | DONE
#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
-* DOING Fancy music-player UI
+* IMPLEMENTED Fancy music-player UI
:PROPERTIES:
:ID: af4f2688-ce7d-43f5-82e5-595a603e2593
:END:
+- 2026-07-09 Thu @ 17:50:32 -0500 — Craig ran the Phase-3 visual VERIFY and found no issues: hero cover art, serif title, amber accents, advancing bar, on-air marker, and the plain-text fallback all pass. All three phases built and pushed. DOING -> IMPLEMENTED.
- 2026-07-06 Mon @ 15:14:06 -0500 — spec-response Phase 6: decomposed into 3 build tasks + flip-to-IMPLEMENTED under the todo.org parent (stamped :SPEC_ID: af4f2688), Phase-3 visual VERIFY filed. READY -> DOING.
- 2026-07-06 Mon @ 15:14:06 -0500 — spec-response: all 7 findings dispositioned (5 accept, 2 modify, 0 reject), Review findings [7/7]. Both blockers resolved and folded in. Decisions [5/5], no blocking open → Ready. DRAFT -> READY.
- 2026-07-06 Mon @ 15:14:06 -0500 — spec-review: Not ready. Decisions [5/5] but Review findings [0/7], two :blocking: (progress-bar data source undefined; row rendering overloads cj/music--track-description). Stays DRAFT until dispositioned.
@@ -15,7 +16,7 @@
- 2026-07-06 Mon @ 14:38:15 -0500 — drafted.
* Metadata
-| Status | doing |
+| Status | implemented |
|----------+-------------------------------------------------------------------|
| Owner | Craig Jennings |
|----------+-------------------------------------------------------------------|
diff --git a/docs/specs/2026-07-06-radio-browser-lookup-spec.org b/docs/specs/2026-07-06-radio-browser-lookup-spec.org
index 637ec4f0..c65de850 100644
--- a/docs/specs/2026-07-06-radio-browser-lookup-spec.org
+++ b/docs/specs/2026-07-06-radio-browser-lookup-spec.org
@@ -4,10 +4,12 @@
#+TODO: TODO | DONE
#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
-* DOING Radio-browser lookup
+* IMPLEMENTED Radio-browser lookup
:PROPERTIES:
:ID: 4839b2c8-0552-4029-9e0f-4bf69b9a4dcd
:END:
+- 2026-07-09 Thu @ 17:50:32 -0500 — Craig ran the queue-first VERIFY (n / t / m picks, save on v, file-playlist save unchanged, empty and offline searches) and found no issues. DOING -> IMPLEMENTED.
+- 2026-07-08 Wed @ 10:16:25 -0500 — model revision (Craig): queue-first, save-on-request. The lookup (and the manual m creator) no longer writes .m3u files at pick time; picked stations become url tracks in the queue (name/uuid/favicon as track properties) and play immediately. Saving is the normal playlist save (moved to w — the earlier S binding was shadowed by emms-stop): an all-stream queue saves into the MPD playlist dir, the plain station name pre-fills the prompt (the "-Radio" filename suffix is retired), and a custom .m3u emitter writes the station metadata back out so load round-trips names and cover art. First pick names a multi-station save. Still DOING.
- 2026-07-06 Mon @ 14:04:53 -0500 — build UX changes (Craig, during Phase 2 verify): pulled tag search from vNext into v1; the feature is a Radio row in the playlist buffer (n: by name, t: by tag, m: enter manually) rather than one command on S; single mode moved off t to s and emms-stop off s to S; created filenames carry a "-Radio" suffix. Still DOING.
- 2026-07-06 Mon @ 13:08:32 -0500 — spec-response Phase 6: decomposed into build tasks in todo.org (parent stamped :SPEC_ID:); READY -> DOING. Build under way.
- 2026-07-06 Mon @ 13:01:55 -0500 — spec-response: all 7 findings dispositioned (6 accept/modify, 1 resolved via new Decision 5); decisions [5/5], findings [7/7]. No blocking finding remains; readiness rubric re-run on the expanded spec passes. DRAFT -> READY. Awaiting Craig's go to decompose into build tasks (spec-response Phase 6, flips READY -> DOING).
@@ -16,7 +18,7 @@
- 2026-07-06 Mon @ 10:01:27 -0500 — drafted.
* Metadata
-| Status | doing |
+| Status | implemented |
|----------+-------------------------------------------------------------------|
| Owner | Craig Jennings |
|----------+-------------------------------------------------------------------|
diff --git a/docs/specs/2026-07-10-org-workflow-doctor-spec.org b/docs/specs/2026-07-10-org-workflow-doctor-spec.org
new file mode 100644
index 00000000..b7f5db6c
--- /dev/null
+++ b/docs/specs/2026-07-10-org-workflow-doctor-spec.org
@@ -0,0 +1,260 @@
+#+TITLE: Org workflow doctor — Spec
+#+AUTHOR: Craig Jennings
+#+DATE: 2026-07-10
+#+TODO: TODO | DONE
+#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
+
+* CANCELLED Org workflow doctor
+:PROPERTIES:
+:ID: c0e06025-a3b8-4238-a9a0-07f9e55913f4
+:END:
+- 2026-07-10 Fri @ 05:58:00 -0500 — CANCELLED. The feature has no job. Craig asked what the doctor buys when it refuses to install anything, and the answer is nothing that isn't already there. Every external binary is already guarded at its point of use, with a clear message: hugo-config guards hugo and the file-manager opener, org-webclipper guards pandoc, org-export-config guards zathura, and ox-pandoc guards itself upstream (ox-pandoc.el:1533, 1970). Package availability surfaces at load or first use. Path checks were the only genuinely new capability, and a startup warning about a missing org-dir contradicts this spec's own goal of keeping startup quiet, while an on-demand check nobody remembers to run is worth roughly nothing. DRAFT -> CANCELLED.
+- 2026-07-10 Fri @ 00:29:10 -0500 — drafted.
+
+* Postmortem
+
+The spec cleared spec-create's Phase 0 bar narrowly, and that was the signal to stop
+and ask what the feature bought over the mechanisms already in the tree. It wasn't
+taken.
+
+The =Reuse & lost opportunities= dimension exists to catch exactly this. It was
+filled in, it named =cj/executable-find-or-warn=, and it dismissed the helper in one
+clause for warning as a side effect. Warning as a side effect is the feature. The
+dimension was answered without being used.
+
+Three claims in the surrounding audit came from grepping for a helper's *name* rather
+than reading for the *behavior*, and all three were wrong: the org modules were said
+not to guard their binaries when they guard them at the point of use, which is the
+better place. Reading corrected the claim each time.
+
+The one real finding this line of work produced landed elsewhere and stands: ledger
+buffers were never linted, fixed in 55b85754.
+
+* Metadata
+| Status | cancelled |
+|----------+----------------------------------------------------------|
+| Owner | Craig Jennings |
+|----------+----------------------------------------------------------|
+| Reviewer | Craig Jennings |
+|----------+----------------------------------------------------------|
+| Related | [[file:../../todo.org][todo.org]] — "Add an Org workflow health check command" |
+|----------+----------------------------------------------------------|
+
+* Summary
+
+A single on-demand command, =cj/org-workflow-doctor=, that checks whether the Org
+workflow's prerequisites are actually present: the files and directories it reads
+and writes, the external programs it shells out to, and the optional packages it
+defers loading. It reports what it found and never changes anything.
+
+Today a missing prerequisite surfaces at command time, in whatever shape the first
+module to trip over it happens to produce. The doctor moves that discovery to a
+moment the user chose.
+
+* Problem / Context
+
+#+begin_quote
+*Correction, 2026-07-10.* The premise below is false and the body is left intact as
+the record of the mistake. Every external binary *is* checked, at its point of use:
+=hugo-config= guards hugo and the file-manager opener, =org-webclipper= guards
+pandoc, =org-export-config= guards zathura, and =ox-pandoc= guards itself upstream.
+The claim came from grepping for =cj/executable-find-or-warn= by name instead of
+reading the modules for the behavior. See the Postmortem above.
+#+end_quote
+
+The Org workflow spans many modules, and each depends on some mix of a personal
+path (=org-dir=, =roam-dir=), an external binary (pandoc, hugo), and an optional
+package that loads lazily (=org-noter=, =org-web-tools=). None of those
+dependencies is checked anywhere.
+
+When one is missing, the failure appears wherever the first module happens to hit
+it. A missing =contacts-file= surfaces as a capture template erroring mid-capture.
+An absent pandoc surfaces as a shell command returning nothing useful. The user
+learns about a broken prerequisite at the least convenient moment, and the message
+rarely names the prerequisite.
+
+Nothing about this is hard. It just isn't anywhere, and the checks are scattered
+across modules that each know only their own corner.
+
+* Goals and Non-Goals
+
+** Goals
+- One command reports the health of every Org workflow prerequisite.
+- The check never mutates user data. It reads, it does not create or repair.
+- The result is structured data, so it is unit-testable and can be rendered to
+ either the echo area or a buffer without recomputing.
+- Startup stays quiet. Nothing runs unless asked.
+
+** Non-Goals
+- It will not fix anything. No creating a missing directory, no installing a
+ package, no offering to. A doctor that repairs is a different, riskier command.
+- It will not check every package in the config, only the Org workflow's.
+- It will not run on a timer, a hook, or at startup.
+- It will not be a general config linter. The scope is the Org workflow.
+
+** Scope tiers
+- v1: the seven paths, the two external binaries, the six optional packages, a
+ structured result, and a rendering to a buffer.
+- Out of scope: repair actions, non-Org prerequisites, scheduled runs.
+- vNext: a =--fix= variant that offers to create missing directories after
+ confirmation; checking that =org-agenda-files= entries all resolve.
+
+* Design
+
+** For the caller
+
+=M-x cj/org-workflow-doctor= opens a report buffer listing every prerequisite with
+its status. A prerequisite is =ok=, =missing=, or =skipped= (checked something the
+user hasn't configured). With a prefix argument the command reports a one-line
+summary to the echo area instead, for a quick "is anything broken?" glance.
+
+Nothing on disk changes. Running it twice produces the same report.
+
+** For the implementer
+
+The command splits in two, per the interactive-versus-internal rule.
+
+=cj/org-workflow--check= is pure with respect to user data: it probes the
+environment and returns a list of plists, one per prerequisite, each carrying
+=:name=, =:kind= (=path= / =executable= / =package=), =:status= and =:detail=. It
+takes no arguments and prompts for nothing, so a test can call it directly against
+a temp =user-emacs-directory= and assert on the structure.
+
+=cj/org-workflow-doctor= is the thin interactive wrapper: call the internal,
+render the result, done.
+
+*** The probe each kind uses
+
+Paths are checked with =file-exists-p= against the variable's value, and reported
+=skipped= when the variable is unbound or nil rather than =missing= — an unset
+=cj/hugo-content-org-dir= means the user doesn't publish with Hugo, which is not a
+fault.
+
+Executables are checked with =executable-find=.
+
+Packages are the one place a naive implementation gets it wrong, and this is the
+decision the spec exists to record. The obvious probe is =featurep=, and it is
+incorrect here: this config defers loading, so =featurep= returns nil for a package
+that is installed and perfectly healthy. Probed on 2026-07-10, =org-noter= and
+=org-web-tools= both report =(featurep) => nil= while =(locate-library) => t=. A
+doctor built on =featurep= would report a false failure for precisely the packages
+the config is designed to load lazily, which is worse than no doctor: it teaches
+the user to ignore it.
+
+=locate-library= answers the question actually being asked, which is "can this load
+when something needs it?" rather than "has it loaded already?".
+
+* Alternatives considered
+
+*** Probe packages with =featurep=
+- Good, because: it is the first thing that comes to mind and costs nothing.
+- Bad, because: it reports false failures for every lazily-loaded package, which is
+ most of them. Verified, not theorised.
+- Neutral, because: it would be correct in a config that loads everything eagerly.
+
+*** Check prerequisites at startup instead of on demand
+- Good, because: the user learns about a broken prerequisite before they need it.
+- Bad, because: it costs startup time on every launch to answer a question asked a
+ few times a year, and it puts warnings in front of a user who didn't ask.
+- Neutral, because: a doctor command can be run from a startup hook later if the
+ cost turns out to be trivial.
+
+*** Have each module check its own prerequisites
+- Good, because: the check lives next to the thing that needs it.
+- Bad, because: this is the status quo, and the problem is that the checks don't
+ compose into an answer to "is my Org workflow healthy?".
+- Neutral, because: the doctor doesn't prevent a module from also checking.
+
+* Decisions [2/2]
+
+** DONE Probe optional packages with =locate-library=, not =featurep=
+Context: the config loads Org packages lazily, so a healthy package is routinely
+unloaded. Probed 2026-07-10: =org-noter= and =org-web-tools= are both
+=featurep=-nil and =locate-library=-non-nil.
+
+Decision: we will probe package availability with =locate-library=.
+
+Consequences: the doctor answers "can this load?", which is the question that
+matters, and it stops reporting false failures for deferred packages. Harder: the
+doctor cannot distinguish "installed but broken on load" from "installed and fine",
+because it deliberately does not load anything. That is the right trade for a
+read-only check, and a load error surfaces at use time anyway.
+
+** DONE An unset optional path reports =skipped=, not =missing=
+Context: not every user of this config publishes with Hugo or uses reveal.js. An
+unbound or nil =cj/hugo-content-org-dir= is a configuration choice, not a fault.
+
+Decision: we will report =skipped= when a path variable is unbound or nil, and
+=missing= only when it holds a value that does not resolve on disk.
+
+Consequences: the report stays honest, so a clean report means something. Harder:
+the status vocabulary grows a third value, and the renderer has to distinguish
+three states rather than two.
+
+* Implementation phases
+
+1. *The internal, with tests.* =cj/org-workflow--check= plus its three probe
+ helpers (path, executable, package). Tests drive real state: a temp directory
+ that exists and one that doesn't, an executable that resolves and a nonsense
+ name, a library that locates and one that doesn't. Tree is working; nothing is
+ bound to a key yet.
+2. *The renderer and the command.* =cj/org-workflow-doctor=, the report buffer, and
+ the prefix-argument echo-area summary. Tests cover the rendering of a synthetic
+ result list, not the environment.
+
+* Acceptance criteria
+
+- =cj/org-workflow--check= returns one plist per prerequisite, each with =:name=,
+ =:kind=, =:status= and =:detail=.
+- A path variable holding a resolving directory reports =ok=; one holding a
+ nonexistent path reports =missing=; one unbound or nil reports =skipped=.
+- =org-noter= and =org-web-tools= report =ok= on this machine despite being
+ unloaded. This is the regression the spec exists to prevent.
+- Running the command twice leaves the filesystem byte-identical.
+- The command is absent from every hook and timer.
+
+* Readiness dimensions
+
+- *Data model & ownership* — the result is generated, ephemeral, and owned by the
+ command. Nothing persists.
+- *Errors, empty states & failure* — a probe that throws is caught per-prerequisite
+ and reported as =missing= with the error text as =:detail=, so one bad probe
+ cannot abort the report.
+- *Security & privacy* — the report prints paths from the user's config. It stays
+ in a local buffer and is never written to disk or transmitted.
+- *Observability* — the report is the observability.
+- *Performance & scale* — fifteen probes, all local filesystem stats. No concern.
+- *Reuse & lost opportunities* — =executable-find= and =locate-library= are the
+ platform's answers; nothing is reimplemented. =cj/executable-find-or-warn=
+ (=system-lib.el=) exists but warns as a side effect, which a read-only check must
+ not do, so the doctor calls =executable-find= directly.
+- *Architecture fit* — a new module, =modules/org-workflow-doctor.el=, requiring
+ nothing but the variables it probes. It must not require the Org modules, or
+ probing them would load them and defeat the lazy-loading it is checking.
+- *Config surface* — none in v1. The prerequisite list is a defconst.
+- *Documentation plan* — module commentary, plus the keybinding if one is added.
+- *Dev tooling* — the existing =make test= covers it. No new target.
+- *Rollout, compatibility & rollback* — additive, read-only, deletable. N/A.
+- *External APIs & deps* — none. Both binaries were verified present on 2026-07-10
+ (=/usr/bin/pandoc=, =/usr/bin/hugo=), and their absence is the case under test.
+
+* Risks, rabbit holes, and drawbacks
+
+The rabbit hole is scope. "Check the Org workflow's prerequisites" slides easily
+into "lint the whole config", and from there into "offer to fix what it finds". The
+non-goals exist to hold that line. If the doctor is useful, a =--fix= variant is a
+separate spec with a separate risk profile, because a command that creates
+directories is no longer read-only.
+
+The smaller risk is the prerequisite list going stale as modules change. A doctor
+that checks the wrong things is worse than none, since it reports health that isn't
+real. The defconst lives next to the probes so it is at least easy to find.
+
+* Review and iteration history
+
+** 2026-07-10 Fri @ 00:29:10 -0500 — Craig Jennings — Author
+What: drafted the spec.
+Why: the task is feature-level, so the speedrun's per-item disposition rule
+delivers a spec rather than an implementation.
+Artifacts: this file. Probed the live daemon for the seven paths, both binaries,
+and the six packages; the =featurep= finding drove the first decision.
diff --git a/docs/specs/gloss-spec.org b/docs/specs/gloss-spec.org
deleted file mode 100644
index 06a7bf50..00000000
--- a/docs/specs/gloss-spec.org
+++ /dev/null
@@ -1,323 +0,0 @@
-#+TITLE: Design — gloss (Glossary Lookup with Online-Sourced Selection)
-#+DATE: 2026-04-28
-#+TODO: TODO | DONE
-#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
-
-* DOING Design — gloss (Glossary Lookup with Online-Sourced Selection)
-:PROPERTIES:
-:ID: 295f9969-ccef-4df9-945b-9e08d8069daf
-:END:
-- 2026-07-04 Sat @ 15:30:41 -0500 — retrofitted to status-heading convention; keyword DOING from existing :STATUS: doing
-
-* Problem
-
-A personal glossary inside Emacs, modelled on the existing =quick-sdcv= UX (=C-h d=) but for self-curated terms rather than packaged dictionaries. =C-h g= prompts for a term (defaulting to word-at-point), looks it up in a single git-tracked org file, and shows the definition in a side buffer that =q= dismisses. On a local miss, the package fetches candidate definitions from an online source, lets the user pick one, and saves it with provenance. The same org file feeds =org-drill= for spaced-repetition study.
-
-The pain point: domain jargon — government acronyms, technical terms, philosophy vocabulary, project-specific names — doesn't live in any general dictionary, so existing tools like =quick-sdcv= can't help. A personal glossary that grows by use (encounter term → save it once → it's permanently looked-up-able and study-card-able) closes that gap.
-
-* Non-Goals
-
-The following are explicitly out of scope for v1. Each is a defensible v2+ topic on its own.
-
-- *Multi-language support.* English only. Wiktionary returns French/Latin/etc. — v1 ignores everything but the =en= key.
-- *Synonyms, cross-references, related terms.* Even when the upstream source returns them, v1 stores only the picked definition.
-- *Audio pronunciation.* Not fetched, not played.
-- *Etymology, usage notes, parsed examples.* Discarded during HTML strip.
-- *Multiple glossaries / domain separation.* One file, one glossary.
-- *Backup or sync infrastructure.* Delegated to git on whatever path =gloss-file= points at.
-- *Org-drill scheduling control.* The exporter prepares entries; =org-drill= itself runs unmodified.
-
-In scope (kept after triage): edit-in-place via =C-h g e=, which jumps to the source file at the entry's heading.
-
-* Approaches Considered
-
-Six approaches evaluated during brainstorm. Three conventional, three tail samples for diversity.
-
-** Recommended: Layered multi-module package
-
-Five =.el= files, each owning one concern: =gloss-core= (data), =gloss-fetch= (network), =gloss-display= (UI), =gloss-drill= (drill export), =gloss= (orchestration entry point). Each layer mocks at its own natural boundary; no layer mocks another layer's internals.
-
-*Why this over the alternatives.* The codebase already prefers layering — =coverage-core= + =coverage-elisp= split, Hugo pure-helpers + interactive wrappers, LSP file-watch defvar + function. The four concerns (data, fetch, display, drill) have genuinely different test boundaries (file I/O, HTTP, mode UI, =org-element=). Mixing them in one file would force overmocking, which the project's testing rules flag as a smell. The package is also public-style — clear module boundaries reward cold readers.
-
-*What's traded away.* About 30 minutes more structural setup at the start, in exchange for boilerplate that may never pay off if the package stays personal forever. Cheap trade against the testing and reading wins.
-
-** Rejected: Single-file quick-sdcv-clone
-
-One =.el= file (~400 lines) covering all four concerns. Simplest path, lowest dependency footprint, but everything (data, HTTP, mode definition, drill) cohabits a single namespace. Test isolation gets awkward; refactor cost grows when one piece needs replacing.
-
-** Rejected: Backend-pluggable registry
-
-A =glossary-backend= protocol covering both local-org and online sources, with =lookup= / =save= / =list= operations. Local and online become interchangeable backends. Real future-proofing, but for v1 with two backends and probably never a third, the protocol is overkill — YAGNI risk. The forward-compat shape we did adopt (the =gloss-fetch-sources= registry, see Architecture) gets the same benefit at a fraction of the design weight, scoped only to where source variety is real.
-
-** Rejected: quick-sdcv + generated StarDict
-
-Round-trip the org file through StarDict format on save; reuse =quick-sdcv='s UI verbatim. Reuses 100% of an existing UI but loses provenance metadata in the round-trip, fights drill (which reads org, not StarDict), and forces a binary intermediate format for what should be a plain-text data store.
-
-** Rejected: Org-roam node per term
-
-Each entry is its own =org-roam= node. Free fuzzy/exact title search, free backlinks. But it's a heavy dependency for an otherwise self-contained package, file-explodes (1000 terms = 1000 files), and contradicts the locked single-file storage decision.
-
-** Rejected: Lazy-reactive minor mode
-
-Passive recognition — =gloss-mode= scans buffer text for known terms, underlines them, hover/click reveals definitions. Different and arguably more-natural mental model, but it reframes the brief (active =C-h g= lookup is what was asked for) and doesn't naturally support online fallback or auto-add. Probably belongs as a v3 feature on top of the layered architecture, not as the architecture itself.
-
-* Design
-
-** Architecture
-
-Five =.el= files:
-
-#+begin_example
-gloss-core.el data layer — org file I/O + in-memory cache
-gloss-fetch.el network layer — Wiktionary REST + HTML strip
-gloss-display.el UI layer — side buffer + picker
-gloss-drill.el drill export — :drill: tag + twosided property
-gloss.el entry point — defcustoms, prefix keymap, user commands
-#+end_example
-
-*Public API by layer.*
-
-=gloss-core=: =gloss-core-lookup TERM=, =gloss-core-save TERM DEFINITION SOURCE=, =gloss-core-list=, =gloss-core-find-buffer-position TERM=.
-
-=gloss-fetch=: =gloss-fetch-definitions TERM= → =(:ok DEFS) | (:empty :no-defs SOURCES :failed SOURCES)=. Internally a registry: =gloss-fetch--sources= alist (source-symbol → fetcher function), walked in order per the user-facing =gloss-fetch-sources= defcustom.
-
-=gloss-display=: =gloss-display-show-entry TERM BODY=, =gloss-display-pick-definition TERM DEFINITIONS=. Defines =gloss-mode= (derived from =special-mode=, =q= quits).
-
-=gloss-drill=: =gloss-drill-export-all=, =gloss-drill-untag-all=. Operates on the org file via =org-element=.
-
-=gloss=: =defcustom gloss-file= (path), =gloss-prefix-map= for =C-h g=, user commands =gloss-lookup=, =gloss-add=, =gloss-edit=, =gloss-fetch-online=, =gloss-drill-export=.
-
-** Data Flow
-
-*Shapes.*
-
-A definition (in flight from fetch through display to save) is a plist:
-
-#+begin_src emacs-lisp
-(:source wiktionary :text "Reference to something earlier in the discourse...")
-#+end_src
-
-An entry (saved in cache and on disk) is a plist:
-
-#+begin_src emacs-lisp
-(:term "anaphora"
- :body "Reference to something earlier in the discourse..."
- :source wiktionary
- :added "2026-04-28"
- :marker #<marker at 1247 in gloss.org>)
-#+end_src
-
-The cache is a hash table, term-string → entry-plist. The org file is the source of truth; the cache is a read-side index.
-
-*Lookup flow (=C-h g=).*
-
-1. Read input — word-at-point if available, else minibuffer prompt.
-2. =gloss-core-lookup TERM=. Cache loaded if cold.
-3. Hit → =gloss-display-show-entry=. Done.
-4. Miss → silent fall-through to =gloss-fetch-definitions TERM=.
-5. Orchestrate on result:
- - 0 definitions or all-failures → side buffer message (see Error Handling).
- - 1 definition → auto-save via =gloss-core-save=, then =gloss-display-show-entry=.
- - >1 definitions → =gloss-display-pick-definition= → user picks → =gloss-core-save= → =gloss-display-show-entry=.
-
-*Add flow (=C-h g a=).*
-
-=gloss-add= prompts for term and body (small temp buffer for multi-line body, =C-c C-c= accepts). =gloss-core-save TERM BODY 'manual=. Then =gloss-display-show-entry=.
-
-*Edit flow (=C-h g e=).*
-
-=gloss-edit= resolves the term to a buffer position via =gloss-core-find-buffer-position=. Opens the org file at that heading in the *source* buffer (not the side buffer). User edits inline. On save, the buffer-local =after-save-hook= refreshes the cache for that single term.
-
-*Drill export (=C-h g D=).*
-
-=gloss-drill-export-all= walks the org file via =org-element=, ensures every term heading has =:drill:= tag and =:DRILL_CARD_TYPE: twosided= property. =M-x org-drill= runs the session — gloss does not wrap or invoke =org-drill= itself.
-
-** Persistence
-
-*File shape.* Single org file at =gloss-file= (default: =(expand-file-name "gloss.org" (or org-directory user-emacs-directory))=). One =* term= heading per entry, alphabetical order maintained on insert. Each entry has a =:PROPERTIES:= drawer with =:SOURCE:= and =:ADDED:=. Body is plain text immediately under the heading.
-
-#+begin_example
-#+TITLE: Glossary
-#+STARTUP: showall
-
-* anaphora
-:PROPERTIES:
-:SOURCE: wiktionary
-:ADDED: 2026-04-28
-:END:
-Reference to something earlier in the discourse...
-
-* SBIR
-:PROPERTIES:
-:SOURCE: wiktionary
-:ADDED: 2026-04-28
-:END:
-Initialism of Small Business Innovation Research...
-#+end_example
-
-After =gloss-drill-export-all=, the heading line gains a =:drill:= tag and the properties drawer gains =:DRILL_CARD_TYPE: twosided=.
-
-*Cache lifecycle.* Hash table loaded lazily on first lookup of the session. Populated by reading =gloss-file= once and parsing with =org-element-parse-buffer=. Subsequent lookups hit the cache directly.
-
-*Cache invalidation.* Four triggers, in order of cost:
-
-1. =gloss-core-save= mutates the cache directly when it writes.
-2. *mtime check on every lookup.* =file-attributes= the file before each =gloss-core-lookup= returns; if mtime > cached-mtime, reload before answering. Sub-millisecond cost; catches every out-of-band edit (other Emacs session, =git pull=, hand-edit, =sed=).
-3. =gloss-edit='s buffer-local =after-save-hook= updates the single edited term immediately; overlaps with #2 but doesn't wait for the next lookup.
-4. Manual =gloss-reload= command — nuclear option for paranoia.
-
-=file-notify-add-watch= rejected: platform-specific backend, async callback complicates the model, mtime path is already sub-millisecond.
-
-*Write strategy.* Append-on-add via direct buffer editing (=find-file-noselect=, insert at the alphabetically-correct heading position, save, kill the buffer if not previously open). No journal, no temp file — org-mode's =auto-save-mode= and the user's git tracking provide durability. Single-user, single-Emacs assumed; concurrent access isn't a concern.
-
-*Alphabetical order.* Maintained on insert via case-insensitive string compare. Cheap; the file stays diff-clean (only the inserted block changes).
-
-** Error Handling
-
-*Per-source status taxonomy.* Five internal values; three user-facing rollups.
-
-#+begin_src emacs-lisp
-;; Internal per-source result:
-(:source SYM :status STATUS :reason STRING)
-
-;; STATUS values:
-;; :ok :defs (def1 def2 ...) — success
-;; :no-defs — server reached, term not there (HTTP 404 or empty 200)
-;; :unreachable — network problem (DNS, refused, timeout)
-;; :server-error — HTTP 5xx, malformed JSON, schema mismatch, HTTP 4xx other than 404/429
-;; :rate-limited — HTTP 429
-#+end_src
-
-*=:reason= strings* carry the technical detail (=timeout (5s)=, =HTTP 503=, =malformed JSON: ...=) and land in =*gloss-debug*=. They are never user-facing.
-
-*User-facing rollup.* =gloss-fetch-definitions= aggregates per-source results into:
-
-#+begin_src emacs-lisp
-(:ok DEFS) ;; any source returned >=1 def
-(:empty :no-defs (...) :failed (...)) ;; everything else
-#+end_src
-
-=:failed= unions =:unreachable=, =:server-error=, =:rate-limited=.
-
-| Result shape | Message |
-|-------------------------------------------+--------------------------------------------------------------------|
-| Every source =:no-defs=, none failed | "No definition for X in Wiktionary." |
-| Every source failed, none =:no-defs= | "Couldn't reach Wiktionary." |
-| Mix of =:no-defs= and failures | "No definition in Wiktionary; couldn't reach DictionaryAPI." |
-| Any =:ok= with defs | Silent on others — picker shows what came back |
-
-When v2 starts surfacing =:rate-limited= regularly, the rollup wording will gain a third visible category. v1 with no-key Wiktionary doesn't need it.
-
-*libxml as a precondition, not a per-source failure.* First time =gloss-fetch-definitions= runs, probe =(libxml-parse-html-region 1 1)= on a temp buffer. If unavailable, online fetching is disabled package-wide for the session with a one-shot =user-error=: "Online fetch requires Emacs built with libxml2; manual add still works." Subsequent online attempts in the session short-circuit to that message.
-
-*Partial-success on per-sense HTML failures.* If libxml is available but fails on a specific sense's content, drop that sense and return the rest. Source status stays =:ok= with N-1 entries; the dropped sense logs to =*gloss-debug*=. A single bad sense doesn't poison the whole source.
-
-*Storage failures.* First call creates =gloss-file= and any missing parent directory with a =#+TITLE: Glossary= header. Permission denied raises =user-error= naming the path. Corrupt org file (=org-element-parse-buffer= raises) preserves the existing cache and surfaces "glossary file corrupt at line N; cache not refreshed" — operations fall back to the stale cache until the user fixes the file and runs =gloss-reload=. Term collision (saving an existing term) prompts: replace, append-with-separator, or cancel.
-
-*Drill.* =org-drill= checked via =featurep= before export runs. If absent: =user-error= with install hint.
-
-*User cancellations.* =C-g= during the picker → no save, side buffer shows the local-miss state. Empty term input from =gloss-add= → re-prompt once, then abort silently. Cancelled at the term-collision prompt → no write.
-
-** Testing
-
-Per-function test files; three categories (Normal/Boundary/Error) per function. TDD by default. Real production code via =require=, never inlined.
-
-*=gloss-core=.* Temp files + real =org-element-parse-buffer=. No mocking — exercises the actual file I/O and parser.
-
-#+begin_example
-test-gloss-core--lookup.el
-test-gloss-core--save.el
-test-gloss-core--invalidate-on-mtime.el
-test-gloss-core--corrupt-file-preserves-cache.el
-test-gloss-core--alphabetical-insert.el
-test-gloss-core--first-call-creates-file.el
-#+end_example
-
-*=gloss-fetch=.* =cl-letf= mock on =url-retrieve-synchronously=, injecting canned response buffers. Captured Wiktionary fixtures in =tests/fixtures/wiktionary-*.json= — real responses for SBIR, anaphora, API, frozen once, replayed forever.
-
-#+begin_example
-test-gloss-fetch--definitions-200-returns-ok.el
-test-gloss-fetch--definitions-404-returns-no-defs.el
-test-gloss-fetch--definitions-500-returns-server-error.el
-test-gloss-fetch--definitions-timeout-returns-unreachable.el
-test-gloss-fetch--strip-html.el
-test-gloss-fetch--multi-source-walks-registry.el
-test-gloss-fetch--libxml-probe.el
-#+end_example
-
-*=gloss-display=.* The candidate-formatting helper =gloss-display--format-candidate PLIST → "[wiktionary] text..."= is pure → full N/B/E coverage. =gloss-display-show-entry= and =gloss-mode= get one smoke test each (Emacs already tests =switch-to-buffer= and major-mode definition).
-
-#+begin_example
-test-gloss-display--format-candidate.el
-test-gloss-display--show-entry-smoke.el
-#+end_example
-
-*=gloss-drill=.* Temp file + real =org-element=. Tests assert tag/property changes on entries.
-
-#+begin_example
-test-gloss-drill--export-all-tags-untagged.el
-test-gloss-drill--export-all-skips-already-tagged.el
-test-gloss-drill--export-all-no-orgdrill-installed.el
-test-gloss-drill--untag-all.el
-#+end_example
-
-*=gloss=.* The orchestration policy =gloss--orchestrate-fetch-result RESULT → SYMBOL= is a pure pattern-matcher. Tested with shaped inputs covering every result variant.
-
-#+begin_example
-test-gloss--orchestrate-fetch-result.el
-#+end_example
-
-*Integration tests.* Three small ones, each with a docstring naming participants per project convention.
-
-#+begin_example
-test-integration-gloss-lookup-flow-local-hit.el
-test-integration-gloss-lookup-flow-online-fall-through.el
-test-integration-gloss-lookup-flow-online-failure.el
-#+end_example
-
-*Coverage targets.* 90%+ on =gloss-core=, =gloss-fetch=, =gloss-drill=, and pure helpers in =gloss-display= / =gloss=. 70%+ on display mode-glue. Overall ≥80%.
-
-** Observability
-
-*=*gloss-debug*= log buffer.* Off until =gloss-debug= defcustom is non-nil, or session-only =gloss-toggle-debug= flips it. One timestamped, layer-prefixed line per significant event.
-
-#+begin_example
-2026-04-28 11:14:02 [fetch:wiktionary] GET /API → 200, 12 senses
-2026-04-28 11:14:02 [fetch:wiktionary] sense 7 HTML parse failed, dropping
-2026-04-28 11:14:02 [core] cache hit for "anaphora"
-2026-04-28 11:14:09 [core] mtime change detected, reloading cache (47 terms)
-2026-04-28 11:14:11 [save] "API" → wiktionary, 11 alts not saved
-#+end_example
-
-Per-source statuses from Error Handling land here verbatim. No personal data beyond user-supplied terms.
-
-*=*Messages*= for user-facing events.* Saves, picker-shown, "no definition found" messages — short single-line =message= calls, persisted in =*Messages*= via Emacs idiom. Strict separation: =*Messages*= for things the user did or asked for; =*gloss-debug*= for everything else.
-
-*Inspection commands.*
-
-- =gloss-list-terms= — completing-read over every term in the cache. Pick one to jump to it.
-- =gloss-stats= — small buffer summarizing total terms, breakdown by =:source=, count of drill-tagged entries, file size, cache mtime.
-
-No metrics export, no telemetry, no profiling hooks — v3 territory if the package ever needs them.
-
-* Open Questions (will become ADRs)
-
-Each was decided during the brainstorm. Listed for traceability; each becomes an ADR in the gloss repo's =docs/decisions/=.
-
-- [ ] *ADR-1: storage path default* → =(expand-file-name "gloss.org" (or org-directory user-emacs-directory))=. Rationale: respects the user's existing =org-directory= convention; falls back gracefully.
-- [ ] *ADR-2: auto-fetch on local miss* → silent fall-through with graceful network-failure path. Rationale: y/n prompt is yes 99% of the time and an annoyance the other 1%; the offline case is better handled by detecting the failure than by pre-asking permission.
-- [ ] *ADR-3: drill direction* → =:DRILL_CARD_TYPE: twosided=. Rationale: tests both recognition and recall over time without doubling the deck.
-- [ ] *ADR-4: HTML strip strategy* → =libxml-parse-html-region= (plain text only, no italic/bold preservation). Rationale: more robust than regex on edge cases; libxml2 is standard on Linux/Mac; ~30 lines.
-
-* Next Steps
-
-1. *Scaffold the repo.* =~/code/gloss= with the claude-template structure: =.ai/= and =todo.org= and =inbox/= gitignored, =Makefile= for tests/lint/compile, =README.org= placeholder, =LICENSE=, package skeleton (=gloss.el= with package-header autoload entry).
-2. *Set up remotes.* Bare repo on cjennings.net at =/var/cjennings/git/gloss.git/= with the existing post-receive hook pattern that mirrors to =github.com/cjennings/gloss=.
-3. *Decompose into todo.org tasks.* One TODO per layer, in implementation order: core → fetch → display → drill → entry-point → integration tests → README. Each task carries its acceptance criteria from this design.
-4. *Implement v1 layer by layer*, TDD per project rules. Run =/start-work= once per task.
-5. *First-week shakedown.* Use the package on real terms for a week. File issues against any rough edges as v1.1 tasks.
-6. *Record the four ADRs* in =docs/decisions/= once the repo exists.
-
-* Status
-
-Draft. Pending: repo scaffold, ADR records, implementation.
diff --git a/docs/specs/init-load-graph-spec.org b/docs/specs/init-load-graph-spec.org
index 0feebfc9..33ed0d34 100644
--- a/docs/specs/init-load-graph-spec.org
+++ b/docs/specs/init-load-graph-spec.org
@@ -8,6 +8,7 @@
:PROPERTIES:
:ID: e1fd137e-e164-42f4-a658-f4d32fbe3228
:END:
+- 2026-07-10 Fri @ 22:50:03 -0500 — reconciled the programming target to shipped decisions: generic LSP policy consolidated under =prog-general= (not =prog-lsp=, which was folded in and deleted, commit dfdb3580), and tree-sitter auto-install gated to ='prompt=. Both were owned items of this spec; recording the outcome, keyword stays DOING for the remaining load-graph work.
- 2026-07-04 Sat @ 15:30:41 -0500 — retrofitted to status-heading convention; keyword DOING from existing :STATUS: doing
* Status
@@ -312,7 +313,7 @@ Category key:
| =video-audio-recording= | O/D/S | command-loaded | External process/device probing only on command. |
| =transcription-config= | O/D/P | command-loaded | Auth/process workflow. |
| =weather-config= | O/D/P | command-loaded | Optional command. |
-| =prog-general= | C/P/S | eager or hooks | Projectile, treesit policy, LSP ownership concerns. |
+| =prog-general= | C/P/S | eager or hooks | Projectile, treesit 'prompt, sole LSP policy owner. |
| =test-runner= | C/L | eager command entry | Test keymap and project-scoped state. |
| =vc-config= | C/P | eager command entry | Magit/git keymap; clone command hardening separate. |
| =flycheck-config= | C/P | hooks | General linting. |
@@ -320,7 +321,6 @@ Category key:
| =prog-c= | D/P | mode-loaded | C hooks and compile command. |
| =prog-go= | D/P | mode-loaded | Go hooks/LSP. |
| =prog-lisp= | D/P | mode-loaded | Lisp package config. |
-| =prog-lsp= | C/P | package policy owner | Should consolidate generic LSP policy. |
| =prog-shell= | D/P/S | mode-loaded | after-save executable hook should be opt-in or scoped. |
| =prog-python= | D/P | mode-loaded | Python hooks/LSP. |
| =prog-webdev= | D/P | mode-loaded | Webdev modes/LSP. |
@@ -603,15 +603,20 @@ Programming target:
- Keep generic programming defaults and F-key command entry points available.
- Load language-specific modules by major mode.
-- Consolidate generic LSP policy under =prog-lsp=.
- - Move to =prog-lsp=: global LSP toggles such as =lsp-idle-delay=,
- =lsp-log-io=, =lsp-enable-folding=, =lsp-enable-snippet=,
- =lsp-headerline-breadcrumb-enable=, and file-watch ignore lists.
- - Keep per-language: server client settings such as
- =lsp-clients-clangd-args= and =lsp-pyright-*=, plus language-mode hook
- wiring.
-- Tree-sitter grammar auto-install is always on; the project policy is global
- allow. =treesit-auto-install= is =t= without per-language conditionals.
+- Generic LSP policy is consolidated under =prog-general= (done 2026-07-10, commit
+ dfdb3580). The original plan named =prog-lsp= as owner, but that module was
+ required by nothing and never loaded, so its config was dead; folding it into the
+ module that actually loads (=prog-general=) and deleting it was the working fix.
+ - In =prog-general=: global LSP toggles such as =lsp-idle-delay=, =lsp-log-io=,
+ =lsp-enable-folding=, =lsp-enable-snippet=, the quiet-UI toggles, and the
+ file-watch ignore list.
+ - Kept per-language: server client settings such as =lsp-clients-clangd-args= and
+ =lsp-pyright-*=, plus the =lsp-deferred= language-mode hook wiring.
+ - Remaining: several language modules call =lsp-deferred= from both a local setup
+ function and a package hook; collapse each to one hook path per language.
+- Tree-sitter grammar auto-install is gated to ='prompt= (done). Batch/test runs never
+ auto-install, and =cj/install-treesit-grammars= is the explicit bootstrap for a new
+ machine.
Org target:
@@ -625,9 +630,10 @@ Org target:
=cj-cache.el= extraction is owned by utility-consolidation Phase 5 and may
follow.
-The =prog-lsp= consolidation and tree-sitter policy decisions are owned by this
-load-graph project. Utility consolidation owns reusable helper extraction, not
-programming policy.
+The LSP consolidation and tree-sitter policy decisions are owned by this
+load-graph project. Both landed 2026-07-10 (LSP under =prog-general=, tree-sitter at
+='prompt=). Utility consolidation owns reusable helper extraction, not programming
+policy.
Exit criteria:
diff --git a/docs/specs/messenger-unification-spec.org b/docs/specs/messenger-unification-spec.org
index c92ba1a7..7847fd04 100644
--- a/docs/specs/messenger-unification-spec.org
+++ b/docs/specs/messenger-unification-spec.org
@@ -8,6 +8,7 @@
:PROPERTIES:
:ID: 4bfc2011-8ffc-4765-8886-91df12141171
:END:
+- 2026-07-14 Tue @ 01:10:00 -0500 — premise shift: the signel client was retired to archive/ (agents drive Signal via signal-cli), so the registry example, the "signel remains the running reference" language, and the backend list all need a rewrite before this leaves DRAFT — the live in-Emacs backends are now telega and Slack, with smoke (~/code/smoke) still the future native adopter. Also landed since the 06-11 survey: slack-config gained signel-shape notification hardening (c69f2f56) and telega notifications are on (5bc5ef7a), so the shared cj/messenger-notify extraction has two live call sites ready.
- 2026-07-04 Sat @ 15:30:41 -0500 — retrofitted to status-heading convention; keyword DRAFT from existing :STATUS: not-started (held open for more ideas)
* Problem
diff --git a/docs/specs/signal-client-spec.org b/docs/specs/signal-client-spec.org
index 13f67115..56c9ea1a 100644
--- a/docs/specs/signal-client-spec.org
+++ b/docs/specs/signal-client-spec.org
@@ -3,10 +3,12 @@
#+TODO: TODO | DONE
#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED
-* DOING Design: Signal client in Emacs (forked signel)
+* IMPLEMENTED Design: Signal client in Emacs (forked signel)
:PROPERTIES:
:ID: 0cabd6ee-c458-47b5-a8af-3ee054b25821
:END:
+- 2026-07-14 Tue @ 01:10:00 -0500 — RETIRED from the config (Craig's call, ~an hour after the IMPLEMENTED flip): agents drive Signal via signal-cli / signal-mcp, so the interactive in-Emacs client earns no keep. signal-config.el and its seven test files moved to archive/ (see archive/README.org); the C-; M prefix unregistered; the ~/code/signel fork repo untouched. Keyword stays IMPLEMENTED as the honest record of what was built.
+- 2026-07-14 Tue @ 01:01:32 -0500 — IMPLEMENTED (Craig's call): v1 is shipped and in daily use — forked signel engine, contact picker with cached contacts (lifecycle hardened 703b4841 / 1296cc45), notifications with sound gating and script-with-fallback delivery, bottom-30% window rule. Next-generation client work belongs to the smoke project (~/code/smoke, its own architecture spec), which the messenger-unification spec designates as the ground-up replacement; signal-config.el stays the running reference until smoke reaches parity.
- 2026-07-04 Sat @ 15:30:41 -0500 — retrofitted to status-heading convention; keyword DOING from existing :STATUS: doing
* Problem