diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/design/ai-kb-shared-roam-brainstorm.org | 2 | ||||
| -rw-r--r-- | docs/design/module-inventory.org | 2 | ||||
| -rw-r--r-- | docs/design/music-config-without-emms-review.org | 2 | ||||
| -rw-r--r-- | docs/design/signal-client-review.org | 2 | ||||
| -rw-r--r-- | docs/design/utility-inventory.org | 6 | ||||
| -rw-r--r-- | docs/specs/ai-kb-spec.org (renamed from docs/design/ai-kb.org) | 4 | ||||
| -rw-r--r-- | docs/specs/ai-vterm-spec-superseded.org (renamed from docs/design/ai-vterm.org) | 4 | ||||
| -rw-r--r-- | docs/specs/cache-helper-design-spec-implemented.org (renamed from docs/design/cache-helper-design.org) | 6 | ||||
| -rw-r--r-- | docs/specs/company-to-corfu-migration-spec.org (renamed from docs/design/company-to-corfu-migration.org) | 4 | ||||
| -rw-r--r-- | docs/specs/coverage-spec-implemented.org (renamed from docs/design/coverage.org) | 4 | ||||
| -rw-r--r-- | docs/specs/debug-profiling-spec.org (renamed from docs/design/debug-profiling.org) | 4 | ||||
| -rw-r--r-- | docs/specs/dev-setup-project-spec.org (renamed from docs/design/dev-setup-project.org) | 4 | ||||
| -rw-r--r-- | docs/specs/dupre-clear-theme-spec.org (renamed from docs/design/dupre-clear-theme.org) | 6 | ||||
| -rw-r--r-- | docs/specs/face-font-diagnostic-popup-spec-implemented.org | 197 | ||||
| -rw-r--r-- | docs/specs/flycheck-modeline-customization-spec-implemented.org (renamed from docs/design/flycheck-modeline-customization.org) | 4 | ||||
| -rw-r--r-- | docs/specs/gloss-spec-doing.org (renamed from docs/design/gloss.org) | 4 | ||||
| -rw-r--r-- | docs/specs/gptel-gh-tool-spec.org (renamed from docs/design/gptel-gh-tool.org) | 6 | ||||
| -rw-r--r-- | docs/specs/gptel-git-tools-magit-backend-spec.org (renamed from docs/design/gptel-git-tools-magit-backend.org) | 4 | ||||
| -rw-r--r-- | docs/specs/gptel-network-tools-spec.org (renamed from docs/design/gptel-network-tools.org) | 6 | ||||
| -rw-r--r-- | docs/specs/init-load-graph-spec-doing.org (renamed from docs/design/init-load-graph.org) | 12 | ||||
| -rw-r--r-- | docs/specs/keybinding-console-safety-spec-doing.org (renamed from docs/design/keybinding-console-safety-spec.org) | 8 | ||||
| -rw-r--r-- | docs/specs/mcp-el-gptel-integration-spec-doing.org (renamed from docs/design/mcp-el-gptel-integration.org) | 6 | ||||
| -rw-r--r-- | docs/specs/messenger-unification-spec.org (renamed from docs/design/messenger-unification-spec.org) | 142 | ||||
| -rw-r--r-- | docs/specs/music-config-without-emms-spec.org (renamed from docs/design/music-config-without-emms.org) | 4 | ||||
| -rw-r--r-- | docs/specs/org-faces-spec-implemented.org | 154 | ||||
| -rw-r--r-- | docs/specs/signal-client-spec-doing.org (renamed from docs/design/signal-client.org) | 4 | ||||
| -rw-r--r-- | docs/specs/theme-studio-package-faces-spec-doing.org (renamed from docs/design/theme-studio-package-faces-spec.org) | 4 | ||||
| -rw-r--r-- | docs/specs/theme-studio-palette-generator-spec-doing.org | 298 | ||||
| -rw-r--r-- | docs/specs/theme-studio-perceptual-color-metrics-spec-implemented.org (renamed from docs/design/theme-studio-perceptual-color-metrics-spec.org) | 4 | ||||
| -rw-r--r-- | docs/specs/theme-studio-preview-locate-spec.org | 148 | ||||
| -rw-r--r-- | docs/specs/theme-studio-seeding-engine-spec-doing.org (renamed from docs/design/theme-studio-seeding-engine-spec.org) | 6 | ||||
| -rw-r--r-- | docs/specs/theme-studio-semantic-theme-architecture-spec.org | 266 | ||||
| -rw-r--r-- | docs/specs/theme-studio-structured-output-spec.org | 157 | ||||
| -rw-r--r-- | docs/specs/utility-consolidation-spec-doing.org (renamed from docs/design/utility-consolidation.org) | 14 | ||||
| -rw-r--r-- | docs/specs/vterm-to-ghostel-migration-spec-implemented.org (renamed from docs/design/vterm-to-ghostel-migration-spec.org) | 6 | ||||
| -rw-r--r-- | docs/theme-studio-palette-generator-spec.org | 241 |
36 files changed, 1479 insertions, 266 deletions
diff --git a/docs/design/ai-kb-shared-roam-brainstorm.org b/docs/design/ai-kb-shared-roam-brainstorm.org index e42e2b006..c2467380b 100644 --- a/docs/design/ai-kb-shared-roam-brainstorm.org +++ b/docs/design/ai-kb-shared-roam-brainstorm.org @@ -410,7 +410,7 @@ This layer answers: * Possible next task Convert this brainstorm into a concrete design delta for the existing -=docs/design/ai-kb.org= and the open =Implement ai-kb= task: +=docs/specs/ai-kb-spec.org= and the open =Implement ai-kb= task: - add agent query triggers; - specify personal-roam access boundaries; diff --git a/docs/design/module-inventory.org b/docs/design/module-inventory.org index 2d4baf81a..eeb824b57 100644 --- a/docs/design/module-inventory.org +++ b/docs/design/module-inventory.org @@ -4,7 +4,7 @@ * Purpose -Living per-module inventory for the [[file:init-load-graph.org][init.el load-graph refactor]]. The +Living per-module inventory for the [[id:e1fd137e-e164-42f4-a658-f4d32fbe3228][init.el load-graph refactor]]. The spec's module-category table is the seed; this file is the per-module truth as each module is inspected and classified. A module moves from [[*Pending classification][Pending classification]] into [[*Classified modules][Classified diff --git a/docs/design/music-config-without-emms-review.org b/docs/design/music-config-without-emms-review.org index 67ef0d1b8..1aae670d6 100644 --- a/docs/design/music-config-without-emms-review.org +++ b/docs/design/music-config-without-emms-review.org @@ -3,7 +3,7 @@ #+DATE: 2026-05-15 #+OPTIONS: toc:nil num:nil -Spec reviewed: =docs/design/music-config-without-emms.org= +Spec reviewed: =docs/specs/music-config-without-emms-spec.org= Third pass. This review answers the single question: is the spec in shape to start =/start-work= against? diff --git a/docs/design/signal-client-review.org b/docs/design/signal-client-review.org index 34b4bbda4..7e8a73e91 100644 --- a/docs/design/signal-client-review.org +++ b/docs/design/signal-client-review.org @@ -5,7 +5,7 @@ * Scope reviewed - =.ai/workflows/spec-review.org=. -- =docs/design/signal-client.org=, including the base design, open-question dispositions, initiate-message workflow, architecture additions, accepted caveats, test plan, scope summary, and readiness rubric. +- =docs/specs/signal-client-spec-doing.org=, including the base design, open-question dispositions, initiate-message workflow, architecture additions, accepted caveats, test plan, scope summary, and readiness rubric. - =modules/signal-config.el=, including =cj/signal--parse-contacts=, notify-suppression helpers, private config loading, and current =use-package signel= wiring. - =~/code/signel/signel.el=, including =signel-start=, =signel--send-rpc=, =signel--dispatch=, =signel--handle-error=, =signel--handle-receive=, =signel--insert-msg=, =signel--insert-system-msg=, =signel--send-input=, =signel-chat=, and dashboard commands. - =tests/test-signal-config.el=, covering contact parsing and notify-suppression helpers. diff --git a/docs/design/utility-inventory.org b/docs/design/utility-inventory.org index cf4c13bd3..8438a5924 100644 --- a/docs/design/utility-inventory.org +++ b/docs/design/utility-inventory.org @@ -4,7 +4,7 @@ * Status -Living inventory. Phase 1 of [[file:utility-consolidation.org][utility-consolidation.org]]. Records the current state of helpers identified in the spec's Candidate Extraction Table plus any new candidates discovered during module walkthroughs. Decisions become concrete tasks in =todo.org= for Phase 2+. +Living inventory. Phase 1 of [[id:fc2e3926-b4a1-4b45-92eb-20841e13f655][utility-consolidation-spec-doing.org]]. Records the current state of helpers identified in the spec's Candidate Extraction Table plus any new candidates discovered during module walkthroughs. Decisions become concrete tasks in =todo.org= for Phase 2+. * Scope @@ -82,7 +82,7 @@ Caller counts in the inventory below reflect grep results from 2026-05-10. The c | Symbol | File | Vis | Deps | Side effects | Proposed home / name | Callers (modules) | Tests | Pri | Decision | Rationale | |--------+------+-----+------+--------------+----------------------+-------------------+-------+-----+----------+-----------| -| =cj/modeline-vc-cache-*= helpers (key/get/put/clear/valid-p) | =modeline-config.el:108-140= | private | buffer-local vars | mutates buffer-local state | =cj-cache.el= / =cj/cache-valid-p=, =cj/cache-get=, =cj/cache-put=, =cj/cache-clear= | 1 (within file) | =test-modeline-config-vc-cache.el= | Medium | Defer | Good pattern, but variable-local cache shape differs from the agenda/refile caches. Needs design before extraction. Spec calls out a Phase 5 design addendum at =docs/design/cache-helper-design.org=. | +| =cj/modeline-vc-cache-*= helpers (key/get/put/clear/valid-p) | =modeline-config.el:108-140= | private | buffer-local vars | mutates buffer-local state | =cj-cache.el= / =cj/cache-valid-p=, =cj/cache-get=, =cj/cache-put=, =cj/cache-clear= | 1 (within file) | =test-modeline-config-vc-cache.el= | Medium | Defer | Good pattern, but variable-local cache shape differs from the agenda/refile caches. Needs design before extraction. Spec calls out a Phase 5 design addendum at =docs/specs/cache-helper-design-spec-implemented.org=. | | agenda/refile cache vars and build flags | =org-agenda-config.el=, =org-refile-config.el= | n/a | timers, file scans | scans filesystem, sets vars | =cj-cache.el= / =cj/cache-value-or-rebuild= | 2 | none | Medium | Defer | TTL/build/invalidate lifecycle; higher risk than the modeline cache. Same Phase 5 work. | ** Logging / Warnings @@ -144,7 +144,7 @@ These become =todo.org= entries (or update existing ones) as Phase 2 starts. ** Deferred (track in =todo.org= but no commit yet) -- Cache abstraction (modeline + agenda/refile) -- needs Phase 5 design addendum at =docs/design/cache-helper-design.org=. +- Cache abstraction (modeline + agenda/refile) -- needs Phase 5 design addendum at =docs/specs/cache-helper-design-spec-implemented.org=. - =cj/--open-with-is-launcher-p= -- move when external-open ownership is finalized. - =cj/log-silently= rename -- low value; do during incidental =system-lib= work. - HTML/text helpers (=strip-html=, =clean-text=) -- defer until a second consumer. diff --git a/docs/design/ai-kb.org b/docs/specs/ai-kb-spec.org index 22a9cb9cf..fbd35ca55 100644 --- a/docs/design/ai-kb.org +++ b/docs/specs/ai-kb-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 03742426-35ce-41c5-aed7-d4e248e91833 +:STATUS: not-started +:END: #+TITLE: Design: AI Knowledge Base (ai-kb) #+AUTHOR: Craig Jennings #+DATE: 2026-05-24 diff --git a/docs/design/ai-vterm.org b/docs/specs/ai-vterm-spec-superseded.org index 99526b632..0b6bfb86c 100644 --- a/docs/design/ai-vterm.org +++ b/docs/specs/ai-vterm-spec-superseded.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 3abd0270-e87c-42b7-9b3a-ef60300db99d +:STATUS: superseded +:END: #+TITLE: Design: ai-vterm — in-Emacs Claude launcher #+AUTHOR: Craig Jennings #+DATE: 2026-05-07 diff --git a/docs/design/cache-helper-design.org b/docs/specs/cache-helper-design-spec-implemented.org index 5de0f348c..27c818dcb 100644 --- a/docs/design/cache-helper-design.org +++ b/docs/specs/cache-helper-design-spec-implemented.org @@ -1,10 +1,14 @@ +:PROPERTIES: +:ID: 647c5101-21c2-47bb-aaa7-72c757f45fb7 +:STATUS: implemented +:END: #+TITLE: Cache Helper Design Addendum #+AUTHOR: Craig Jennings #+DATE: 2026-05-10 * Status -Phase 5 design addendum to [[file:utility-consolidation.org][utility-consolidation.org]]. Specifies the cache API to extract before any code moves. +Phase 5 design addendum to [[id:fc2e3926-b4a1-4b45-92eb-20841e13f655][utility-consolidation-spec-doing.org]]. Specifies the cache API to extract before any code moves. * Problem diff --git a/docs/design/company-to-corfu-migration.org b/docs/specs/company-to-corfu-migration-spec.org index 55da081c8..a7b059a3b 100644 --- a/docs/design/company-to-corfu-migration.org +++ b/docs/specs/company-to-corfu-migration-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 68733ba2-37a7-4a7b-bfaa-b845d82ff1e7 +:STATUS: not-started +:END: #+TITLE: Design: Migrate from Company to Corfu (with prescient integration) #+AUTHOR: Craig Jennings #+DATE: 2026-05-15 diff --git a/docs/design/coverage.org b/docs/specs/coverage-spec-implemented.org index acd8b4c43..65734fb3d 100644 --- a/docs/design/coverage.org +++ b/docs/specs/coverage-spec-implemented.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 7d7f4486-fad7-4f0a-bd9a-775bd4cd8f7e +:STATUS: implemented +:END: #+TITLE: Design: Coverage Reporting #+AUTHOR: Craig Jennings #+DATE: 2026-04-22 diff --git a/docs/design/debug-profiling.org b/docs/specs/debug-profiling-spec.org index 3f5792501..5961071b8 100644 --- a/docs/design/debug-profiling.org +++ b/docs/specs/debug-profiling-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: c713b431-ae14-498d-aba9-b84d52f981b6 +:STATUS: not-started +:END: #+TITLE: Design: debug-profiling.el module #+AUTHOR: Craig Jennings #+DATE: 2026-04-26 diff --git a/docs/design/dev-setup-project.org b/docs/specs/dev-setup-project-spec.org index 280b015b2..5d64f368f 100644 --- a/docs/design/dev-setup-project.org +++ b/docs/specs/dev-setup-project-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 596fce5d-1bab-46e7-8567-d4a2e0923091 +:STATUS: not-started +:END: #+TITLE: Design: cj/dev-setup-project #+AUTHOR: Craig Jennings #+DATE: 2026-04-22 diff --git a/docs/design/dupre-clear-theme.org b/docs/specs/dupre-clear-theme-spec.org index 3b88a7d0f..578eb240e 100644 --- a/docs/design/dupre-clear-theme.org +++ b/docs/specs/dupre-clear-theme-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 20df7f50-4759-47ba-9782-8dd25a2e173e +:STATUS: not-started +:END: #+TITLE: dupre-clear — a contrast-first AAA sibling theme #+AUTHOR: Craig Jennings #+DATE: 2026-06-07 @@ -78,7 +82,7 @@ The hardest slot is *blue keywords*: a deep dupre blue (#67809c) is intrinsicall - The session's exploration tooling was a set of throwaway =/tmp/gen-*.py= scripts that render palette + 4-language code samples to HTML and open them in a browser; they include WCAG-contrast and CIEDE2000 (perceptual distance) helpers. Those /tmp files won't survive a reboot — re-derive the helpers (WCAG: relative luminance with the sRGB linearization, contrast = (L1+0.05)/(L2+0.05); CIEDE2000 for separation). The math is also embedded in =tests/test-dupre-theme.el= (the WCAG half). - modus reference palette: =/usr/share/emacs/30.2/etc/themes/modus-vivendi-theme.el= (and the operandi/tinted variants alongside it). -- dupre lineage: dupre ← distinguished (emacs, Kim Silkebaekken) ← vim-distinguished. The dupre palette lives in =themes/dupre-palette.el= + =themes/dupre-faces.el=; swatch PNG at =themes/dupre-palette.png=. +- dupre lineage: dupre ← distinguished (emacs, Kim Silkebaekken) ← vim-distinguished. The dupre theme has since been retired from the tree (recoverable from git history); WIP, the active theme-studio theme, descends from its palette. - The key perceptual lessons from the session (also in the anchor): thin colored text desaturates (muted hues grey out as glyphs — bold helps); a near-black ground forces depth-vs-AAA as a hard tradeoff; Hyprland inactive-window dimming silently shifts colors (disable with =hyprctl keyword decoration:dim_inactive false= during color work). * Open questions diff --git a/docs/specs/face-font-diagnostic-popup-spec-implemented.org b/docs/specs/face-font-diagnostic-popup-spec-implemented.org new file mode 100644 index 000000000..3e8fadcd8 --- /dev/null +++ b/docs/specs/face-font-diagnostic-popup-spec-implemented.org @@ -0,0 +1,197 @@ +:PROPERTIES: +:ID: 98f065cf-8bd5-46a0-ac24-da94d66855ad +:STATUS: implemented +:END: +#+TITLE: Face and Font Diagnostic Popup — Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-14 +#+TODO: TODO | DONE SUPERSEDED CANCELLED + +* Metadata + +| Status | implemented | +|----------+---------------------------------------------------| +| Owner | Craig Jennings | +|----------+---------------------------------------------------| +| Related | [[file:../../todo.org][todo.org: Face and font diagnostic popup at point]] | + +* Summary + +A read-only command that, for the character at point in an ordinary buffer, pops up everything that determines how that character is painted: the full face stack, the effective merged attributes, the real font versus the declared family, and where each attribute came from (theme, config, or inheritance). It exists to answer one question fast — "why does this text look wrong under the theme, and is the fault the theme, my config, or a fallback?" + +* Problem / Context + +Theme work in this config keeps hitting the same wall: a glyph renders in the wrong color and there's no quick way to see why. The cursor showed gold in auto-dimmed buffers; elfeed rendered all-white ignoring its theme assignments. Each of those is a different layer failing — a face remap, an overlay, an unspecified attribute falling through to the default — and the built-in tools don't separate those layers or trace provenance. + +What paints a character is a merge of several sources resolved by the redisplay engine: the default face, then text-property faces (=face= / =font-lock-face=), then overlay faces stacked by priority, all rewritten by any =face-remapping-alist= entries, and finally a font chosen by the fontset that can differ from the face's declared =:family=. To debug a theme issue you need to see each layer, the merged result, and — for each face — whether its current attributes came from the active theme, from config, or from an =:inherit= chain that bottoms out at the default. + +=describe-char= and =C-u C-x == show the character, its faces as links, and the font, but they don't separate the stack by source, don't surface active remaps, and don't trace attribute provenance. The gap is exactly the part that distinguishes a theme bug from a config bug. + +* Goals and Non-Goals + +** Goals +- For the character at point, show the face stack separated by source (text-property, overlay-by-priority, active remaps, default). +- Show the effective merged attributes — the value that wins for each attribute. +- Show the real font (=font-at=) next to the face's declared =:family=, to expose fontset substitution. +- For each face, trace provenance: which theme(s) and/or config set each attribute, the =:inherit= chain, and the unspecified→fallback resolution. +- Present it in a read-only, navigable help-style buffer obeying the project's unified popup placement and dismissal rules. +- Degrade gracefully in out-of-scope buffers: show what can be read plus a banner naming the foreign color source — never a bare refusal. + +** Non-Goals +- No editing of faces, themes, or attributes. This is a diagnostic, not an editor; theme-studio owns editing. +- No reimplementation of =describe-char='s general character report (display tables, composition, char properties beyond faces). +- No coverage of color sources outside the theme/face system as first-class (terminal ANSI palettes, document HTML/CSS, image buffers) — surfaced, not analyzed. +- No persistence, history, or export of diagnostic output. + +** Scope tiers +- v1: char-at-point diagnosis with an optional region-scan mode; the five info groups below; the help-style popup; graceful out-of-scope handling. +- Out of scope: terminal-ANSI buffers, image/PDF buffers, and shr/document-rendered buffers as analyzed targets (they get the banner + best-effort dump). +- vNext: interactivity — "send this face to theme-studio", jump-to-theme-spec actions, and any write path. Logged to todo.org. + +* Design + +The command — call it =cj/describe-face-at-point= (final name an open detail) — reads the character at point and builds a report buffer in five groups. It never mutates buffer or frame state. + +** For the user (what the popup shows) + +1. *Character context.* The character, its codepoint and Unicode name, and its script. Script is what explains fontset routing, so it earns its place even though it's one line. + +2. *Face stack, by source.* The layers that contribute, in merge order, each labeled by where it comes from: + - text-property faces: the =face= and =font-lock-face= properties at point, in list order, anonymous specs shown inline; + - overlay faces: every overlay covering point that carries a =face=, sorted by overlay priority, with a best-effort owner label; + - active remaps: the =face-remapping-alist= entries that apply to faces in the stack (this is the auto-dim layer); + - the default face underneath. + Source separation is the diagnostic — "is this from a text property, an overlay, or a remap?" is half the answer. + +3. *Effective merged attributes.* The winning value per attribute (family, height, weight, slant, foreground, background, underline, overline, strike-through, box, inverse-video). This is what actually paints. + +4. *Real font vs declared family.* The font =font-at= reports as actually used, next to the merged =:family=. A mismatch means the fontset substituted (emoji, CJK, a missing glyph) — a common "why is this one character different" cause. + +5. *Per-face provenance.* For each named face in the stack: which theme(s) set its attributes (=theme-face= property), whether config overrode it (=saved-face= / =customized-face= / a runtime =set-face-attribute=), the =:inherit= chain, and for each unspecified attribute the resolution trace — "=:foreground= unspecified → not set by any theme → no inherit → default fg." That last trace is the direct read on the elfeed-white class of bug. + +The report is a read-only buffer in a dedicated mode, with named faces rendered as buttons that re-run the command's per-face section or call =describe-face= (navigation only — no edits in v1). + +** For the implementer (how it's built) + +A pure core plus a thin interactive wrapper, per the project's interactive/internal split: + +- =cj/--face-diagnosis-at (pos &optional buffer)= → a plist describing the five groups. No prompts, no display. This is the testable unit. +- =cj/describe-face-at-point= (interactive) → calls the core at point, renders the plist into the help buffer, places the window per the unified popup rules. +- Region mode → maps the core over the distinct face-runs in the active region and concatenates. + +Data sources, by group: +- Stack: =get-text-property= for =face= / =font-lock-face=; =overlays-at= filtered to those with a =face=, sorted by =overlay-get … 'priority=; =face-remapping-alist= (buffer-local) intersected with the stack; =get-char-property= as a cross-check on the merged text-prop+overlay face. +- Merged attributes: see the open decision below — Emacs exposes no single "final merged plist" call, so the core folds the ordered stack itself. +- Real font: =font-at=, then =query-font= / =font-info= for its family and name; nil under =--batch=, handled as "unavailable". +- Provenance: =(get FACE 'theme-face)= for theme spec history, =saved-face= / =customized-face= / =face--attribute-from-frame= comparisons for config overrides, and =face-attribute= with the inherit-following argument to produce the resolution trace. + +Buffer classification (group 0, decides scope handling): a predicate inspects =major-mode= derivation and known markers to bucket the buffer as theme-faced (analyze fully), terminal-ANSI, document-shr, or image/no-text. Out-of-scope buckets still render groups 1–2 best-effort and prepend a banner naming the color source. + +* Alternatives Considered + +** Presentation: childframe / posframe popup +- Good, because it floats near point and looks modern. +- Bad, because the report is tall and structured; a childframe is cramped, doesn't scroll naturally, and fights the existing unified-popup policy. +- Neutral, because a posframe could wrap the same render function later if wanted. + +** Presentation: which-key-style transient strip +- Good, because it's lightweight. +- Bad, because it can't hold five groups of structured, navigable, copyable text. Wrong tool for a report. + +** Reuse: extend describe-char instead of a new command +- Good, because describe-char already resolves faces and the font and renders links. +- Bad, because its output is fixed and character-report-shaped; the value here is source-separation and provenance, which would mean rewriting most of its body anyway. Better to study =descr-text.el= for the font/face resolution mechanics and build a focused command than to graft onto a general one. +- Neutral, because we still reuse the same primitives it uses (=font-at=, =get-char-property=). + +** Scope: analyze every buffer uniformly +- Good, because no classifier to write. +- Bad, because in a terminal or an shr buffer the provenance trace is misleading — the color isn't from the theme, so "theme didn't set it" reads as a theme bug when it isn't. The banner exists precisely to stop that false read. + +* Decisions [7/7] +** DONE Granularity: char-at-point with optional region scan +- Context: precise diagnosis wants one character; occasionally you want a whole region surveyed. +- Decision: We will default to the character at point and offer a region-scan mode over the distinct face-runs when a region is active. +- Consequences: easier — the common case is one precise report; harder — region mode must dedupe face-runs and concatenate without flooding the buffer. + +** DONE Provenance is core v1 +- Context: provenance (theme vs config vs inherit, unspecified→fallback) is the whole reason to build this over describe-char. +- Decision: We will treat the per-face provenance trace as required v1 content, not a follow-up. +- Consequences: easier — the tool actually answers theme-vs-config; harder — provenance extraction is the most intricate part and carries Emacs-version risk on the =theme-face= / =saved-face= internals. + +** DONE Include the real-font (fontset) layer +- Context: a face's =:family= can differ from the font actually chosen for a glyph. +- Decision: We will show =font-at='s real font next to the declared family. +- Consequences: easier — catches substitution bugs; harder — =font-at= is nil in batch, so tests must tolerate "unavailable". + +** DONE Presentation: read-only help-style buffer under the unified popup rules +- Context: the report is tall and structured and benefits from scrolling, copy, and face links. +- Decision: We will render into a dedicated read-only buffer and place/dismiss it via the project's unified popup placement and dismissal rules. +- Consequences: easier — idiomatic, navigable, consistent with other popups; harder — depends on the unified-popup policy, whose placement thresholds are still being settled in its own task. + +** DONE Interactivity is vNext +- Context: a "send face to theme-studio" bridge is attractive but is editing-adjacent. +- Decision: We will ship v1 read-only; the theme-studio bridge and any write path are vNext. +- Consequences: easier — v1 stays a safe pure diagnostic; harder — users must round-trip through theme-studio by hand until vNext. + +** DONE Out-of-scope buffers: classify and show everything, with a banner +- Context: a hard refuse in a terminal/shr/image buffer is unhelpful and hides information. +- Decision: We will classify the buffer, render what we can, and prepend a banner naming the foreign color source instead of refusing. +- Consequences: easier — maximal information always, and the boundary teaches itself; harder — the classifier must recognize the buffer buckets reliably enough that the banner isn't wrong. + +** DONE Effective-attribute computation approach +- Owner / by-when: Claude / before Phase 2 implementation. +- Context: Emacs exposes no public call returning the final merged attribute plist for a position (text props + overlays + remaps as the C redisplay merges them). The tool has to produce the "what actually paints" values itself. +- Decision: We fold the ordered, remap-expanded stack manually with =face-attribute=, treating overlays-over-text-props-over-default and applying remaps, and label the merged result as "computed" — accepting that exotic edge cases (relative heights, deep =:inherit= ordering) may diverge slightly from the engine. Alternative under consideration: lift the resolution mechanics from =descr-text.el= / =face-at-point= rather than hand-rolling. +- Consequences: easier — a single explicit merge we can unit-test; harder — fidelity to the real engine isn't guaranteed for corner cases, so the spec stays "Ready with caveats" until the approach is pinned. + +*** Discussion +- Resolved 2026-06-15: implemented as the hand-fold in =cj/--face-diag-merged-attributes= (overlays over text-props over default, remaps expanded ahead of their base), labeled "computed". Pinned by fixtures in =test-face-diagnostic.el= -- overlay-over-text-prop, a default remap, and a face-symbol attribute all resolve correctly. Exotic relative-height / deep-inherit cases may still diverge, accepted per the decision. + +* Implementation phases + +** Phase 1 — Core read model + buffer classifier +=cj/--face-diagnosis-at= returns the plist for groups 0–2 (classification, character context, face stack by source). Pure, no display. Unit-tested against temp-buffer fixtures with planted text properties, overlays, and remaps. Tree stays green. + +** Phase 2 — Merged attributes + real font +Extend the core with group 3 (effective merged attributes, per the resolved computation decision) and group 4 (=font-at= vs declared family, "unavailable" under batch). Unit-tested on the merge fixtures. + +** Phase 3 — Provenance trace +Add group 5: theme/config/inherit provenance and the unspecified→fallback resolution per face. Tested with fixtures that set a face via a loaded theme, via =set-face-attribute=, and leave one attribute unspecified. + +** Phase 4 — Render + popup wiring +The interactive =cj/describe-face-at-point=, the read-only mode with face buttons, region-scan mode, and placement/dismissal via the unified popup rules. Smoke-tested live; the render function tested on a captured plist. + +* Acceptance criteria +- [ ] On a normal prog/text buffer, the popup shows all five groups for the character at point. +- [ ] An overlay face (e.g. region) at point appears in the stack, labeled as an overlay, above the text-property faces. +- [ ] An active =face-remapping-alist= remap (e.g. under auto-dim) appears as the remap layer and is reflected in the merged result. +- [ ] A face with an unspecified =:foreground= shows the resolution trace down to its actual fallback. +- [ ] A glyph using a substituted font (e.g. an emoji) shows a real-font ≠ declared-family mismatch. +- [ ] In a terminal/shr/image buffer, the popup shows a banner naming the color source and still renders what it can. +- [ ] The core (=cj/--face-diagnosis-at=) returns its plist with no prompts and no display side effects, and passes under =make test= (=--batch=). + +* Readiness dimensions +- Data model & ownership: all data is read live from buffer/overlay/face/font state; nothing user-authored, generated, or persisted. The report plist is ephemeral. +- Errors, empty states & failure: no character at point (empty buffer / eob) → a clear "nothing at point" message; =font-at= nil under batch → "font: unavailable (batch)"; out-of-scope buffer → banner, not error. No silent data loss (read-only tool). +- Security & privacy: N/A — reads visible buffer text and face metadata; logs nothing; no credentials. +- Observability: the tool *is* the observability surface. Its own failures surface as in-buffer messages naming the missing piece (e.g. "font backend unavailable"). +- Performance & scale: single character is trivial; region mode is bounded by distinct face-runs in the region — cap or warn past a threshold so a whole-buffer region doesn't generate a huge report. No live/remote dependency. +- Reuse & lost opportunities: reuses =font-at=, =get-char-property=, =face-attribute=, =theme-face=/=saved-face= internals, and the project's unified-popup policy and interactive/internal split. Studies =descr-text.el= rather than forking it. +- Architecture fit & weak points: integration points are the unified-popup placement policy (in flux) and the face/theme internals (=theme-face=, =saved-face=) which are version-sensitive — isolate them behind small accessors so an Emacs-version change touches one place. +- Config surface: the region-run cap is the one likely knob, with a safe default. Possibly a toggle for whether out-of-scope buffers render best-effort or just the banner. +- Documentation plan: a docstring on the command, the keybinding noted in the keybinding map, and a CLAUDE.md/notes pointer only if a non-obvious gotcha surfaces. No user manual needed. +- Dev tooling: existing =make test= / byte-compile / live-reload loop; no new targets. +- Rollout, compatibility & rollback: additive new command + one keybinding; nothing persisted or migrated; rollback is removing the module. No compatibility surface. +- External APIs & deps: N/A — pure Emacs primitives, no external API or package dependency. + +* Risks, Rabbit Holes, and Drawbacks +- *Merge fidelity* (the open decision): a hand-folded attribute merge may diverge from the redisplay engine on exotic cases. Dodge: validate against =describe-char= on a handful of fixtures; label the result "computed"; don't claim pixel-exactness. +- *Provenance internals*: =theme-face= / =saved-face= are not a stable public contract. Dodge: isolate behind accessors; tolerate missing properties as "unknown source" rather than erroring. +- *Unified-popup dependency*: that policy's placement thresholds aren't settled. Dodge: code to the policy's interface, accept whatever defaults it lands on; don't invent a parallel placement scheme here. +- *Overlay owner labeling*: overlays don't record their creator. Dodge: best-effort label from known marker properties; fall back to "(overlay)" without guessing. + +* Review and iteration history +** 2026-06-14 Sun @ 22:30:00 -0500 — Claude (for Craig) — author +- What: initial draft. +- Why: theme debugging keeps hitting layered face/font issues with no tool that separates the layers or traces provenance; agreed to spec before building. +- Artifacts: [[file:../../todo.org][todo.org: Face and font diagnostic popup at point]]; motivating bugs — gold-text-in-auto-dim, elfeed-ignores-theme. diff --git a/docs/design/flycheck-modeline-customization.org b/docs/specs/flycheck-modeline-customization-spec-implemented.org index 25e2c7854..59567be60 100644 --- a/docs/design/flycheck-modeline-customization.org +++ b/docs/specs/flycheck-modeline-customization-spec-implemented.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 76979608-956e-474f-90a8-8d0c958101a0 +:STATUS: implemented +:END: #+TITLE: Design: Flycheck modeline customization #+AUTHOR: Craig Jennings #+DATE: 2026-05-15 diff --git a/docs/design/gloss.org b/docs/specs/gloss-spec-doing.org index 04efc38bf..320b83ebf 100644 --- a/docs/design/gloss.org +++ b/docs/specs/gloss-spec-doing.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 295f9969-ccef-4df9-945b-9e08d8069daf +:STATUS: doing +:END: #+TITLE: Design — gloss (Glossary Lookup with Online-Sourced Selection) #+DATE: 2026-04-28 #+STATUS: Draft diff --git a/docs/design/gptel-gh-tool.org b/docs/specs/gptel-gh-tool-spec.org index a9ba22bb1..80ecc0ab6 100644 --- a/docs/design/gptel-gh-tool.org +++ b/docs/specs/gptel-gh-tool-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: a124dd0f-1f40-4533-aeb8-595d93e20865 +:STATUS: not-started +:END: #+TITLE: Design: Wrap the gh CLI as a GPTel tool #+AUTHOR: Craig Jennings #+DATE: 2026-05-16 @@ -1052,7 +1056,7 @@ Items intentionally deferred: - [[file:gptel-agentic-tool-ideas.org][gptel-agentic-tool-ideas.org]] -- broader agentic-tool design; =gh= sits alongside the MCP integration as the collaboration tier. -- [[file:mcp-el-gptel-integration.org][mcp-el-gptel-integration.org]] -- sibling design; same +- [[id:b4c274c5-8572-4a7b-b657-d315712bd6af][mcp-el-gptel-integration-spec-doing.org]] -- sibling design; same confirm-on-write pattern for safety. - [[https://cli.github.com/manual/][gh CLI manual]] -- subcommand reference. - =gh --version 2.92.0= help output -- verified flag semantics diff --git a/docs/design/gptel-git-tools-magit-backend.org b/docs/specs/gptel-git-tools-magit-backend-spec.org index 94fbb0cec..bd84b0595 100644 --- a/docs/design/gptel-git-tools-magit-backend.org +++ b/docs/specs/gptel-git-tools-magit-backend-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: bd47c9a8-aae1-4a3d-ad5b-b8767f2fd580 +:STATUS: not-started +:END: #+TITLE: Design: gptel git tools on a magit backend #+AUTHOR: Craig Jennings #+DATE: 2026-05-16 diff --git a/docs/design/gptel-network-tools.org b/docs/specs/gptel-network-tools-spec.org index aae2cc2a8..c28d54694 100644 --- a/docs/design/gptel-network-tools.org +++ b/docs/specs/gptel-network-tools-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 6388588c-dac2-4c52-97ad-2343ba1443fc +:STATUS: not-started +:END: #+TITLE: Design: gptel network tools #+AUTHOR: Craig Jennings #+DATE: 2026-05-16 @@ -7,7 +11,7 @@ Draft. Brainstorm output captured from a =/brainstorm= session on 2026-05-16. Sibling to -=docs/design/gptel-git-tools-magit-backend.org= and the broader theme +=docs/specs/gptel-git-tools-magit-backend-spec.org= and the broader theme hierarchy under =** TODO [#B] GPTel Tool Work= in =todo.org=. The conventional vs tail-sample exploration covered three categories diff --git a/docs/design/init-load-graph.org b/docs/specs/init-load-graph-spec-doing.org index 3db2fe854..05dd9e0a3 100644 --- a/docs/design/init-load-graph.org +++ b/docs/specs/init-load-graph-spec-doing.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: e1fd137e-e164-42f4-a658-f4d32fbe3228 +:STATUS: doing +:END: #+TITLE: Design: Untangle the init.el Load Graph #+AUTHOR: Craig Jennings #+DATE: 2026-05-04 @@ -174,7 +178,7 @@ Foundation modules should be able to load in batch mode without package, network, timer, or UI-package side effects. Adding a new Layer 1 module requires a coordinated update to the -=system-lib.el= dependency budget in [[file:utility-consolidation.org][utility-consolidation.org]]. +=system-lib.el= dependency budget in [[id:fc2e3926-b4a1-4b45-92eb-20841e13f655][utility-consolidation-spec-doing.org]]. Topic libraries introduced by the utility project join Layer 1 only when their first consumer is foundation-eager. Otherwise they are Layer 2 and loaded by an @@ -391,7 +395,7 @@ Worked example: ;; Runtime requires: user-constants, seq, subr-x. ;; Direct test load: yes (batch-safe; private config is optional). ;; -;; See also: docs/design/init-load-graph.org, tests/test-calendar-sync.el. +;; See also: docs/specs/init-load-graph-spec-doing.org, tests/test-calendar-sync.el. ;; ;;; Code: #+end_src @@ -448,7 +452,7 @@ Inventory rules: - Every module required by =init.el= must be represented before Phase 2 starts. - Discoveries during later phases update the inventory. - This inventory is independent from the helper inventory owned by - [[file:utility-consolidation.org][utility-consolidation.org]]. + [[id:fc2e3926-b4a1-4b45-92eb-20841e13f655][utility-consolidation-spec-doing.org]]. Exit criteria: @@ -643,7 +647,7 @@ rollback shapes. This sibling project can run beside Phase 2. When explicit-dependency work finds a generic duplicated helper, the sibling project owns the extraction commit when the helper is in scope for that project. See -[[file:utility-consolidation.org][utility-consolidation.org]] for candidate +[[id:fc2e3926-b4a1-4b45-92eb-20841e13f655][utility-consolidation-spec-doing.org]] for candidate helpers, naming rules, dependency budgets, migration phases, and test policy. * Testing Strategy diff --git a/docs/design/keybinding-console-safety-spec.org b/docs/specs/keybinding-console-safety-spec-doing.org index d06c5a279..4a1dec813 100644 --- a/docs/design/keybinding-console-safety-spec.org +++ b/docs/specs/keybinding-console-safety-spec-doing.org @@ -1,9 +1,13 @@ +:PROPERTIES: +:ID: 540bf06b-16b8-46c6-b459-c40d1b9c795d +:STATUS: doing +:END: #+TITLE: Keymap Consolidation — Spec #+AUTHOR: Craig Jennings #+DATE: 2026-06-12 * Metadata -| Status | draft | +| Status | doing | |----------+--------------------------------------------------------------------| | Owner | Craig Jennings | |----------+--------------------------------------------------------------------| @@ -890,7 +894,7 @@ translation block being retired). =C-l= appears only minibuffer-local in - Why: a touched key family broke in GUI and is dead in console; the fix path is cross-cutting (18 keys, a translation layer to retire, a console-safety architecture) with real trade-offs, so it clears the spec bar. -- Artifacts: docs/design/keybinding-console-safety-spec.org; supersedes the +- Artifacts: docs/specs/keybinding-console-safety-spec-doing.org; supersedes the pre-template draft docs/design/keybinding-console-safety.org. ** 2026-06-12 Fri @ 18:30:30 -0500 — Craig Jennings — review response - What: processed Craig's four review comments. Recorded his first-choice diff --git a/docs/design/mcp-el-gptel-integration.org b/docs/specs/mcp-el-gptel-integration-spec-doing.org index 6bac77c54..f22e91959 100644 --- a/docs/design/mcp-el-gptel-integration.org +++ b/docs/specs/mcp-el-gptel-integration-spec-doing.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: b4c274c5-8572-4a7b-b657-d315712bd6af +:STATUS: doing +:END: #+TITLE: Design: Wire mcp.el into GPTel for MCP server access #+AUTHOR: Craig Jennings #+DATE: 2026-05-16 @@ -1420,7 +1424,7 @@ C= so existing GPTel keys aren't disturbed (rev 3). - [[file:gptel-tools-shortlist.org][gptel-tools-shortlist.org]] -- local-tools shortlist; MCP servers slot in as the "external" tier. - [[file:gptel-agentic-tool-ideas.org][gptel-agentic-tool-ideas.org]] -- broader agentic-tool design. -- [[file:gptel-gh-tool.org][gptel-gh-tool.org]] -- sibling design; same confirm-on-write +- [[id:a124dd0f-1f40-4533-aeb8-595d93e20865][gptel-gh-tool-spec.org]] -- sibling design; same confirm-on-write pattern. - [[https://github.com/lizqwerscott/mcp.el][lizqwerscott/mcp.el]] -- upstream. - [[https://github.com/lizqwerscott/gptel-mcp.el][lizqwerscott/gptel-mcp.el]] -- considered and declined; see § diff --git a/docs/design/messenger-unification-spec.org b/docs/specs/messenger-unification-spec.org index 7e8780300..92985f596 100644 --- a/docs/design/messenger-unification-spec.org +++ b/docs/specs/messenger-unification-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 4bfc2011-8ffc-4765-8886-91df12141171 +:STATUS: not-started +:END: #+TITLE: Messenger Unification — Shared Window Placement and Key Conventions #+AUTHOR: Craig Jennings & Claude #+DATE: 2026-06-11 @@ -185,6 +189,18 @@ directions. (registry, predicate, display rule, minor mode, dispatchers), TDD: ERT over the pure parts (registration shape, buffer matching, dispatch with stub fns, nil-verb error). Wire signel; retire its private display entry. + - /Smoke-first parity (Craig 2026-06-16)./ Signal is the least-built backend + and the only one whose UX Craig fully controls (no upstream package fighting + back), so the smoke rebuild is where the shape gets dialed in first: build + smoke to implement every core leaf (=j a u m k C Q=) and the in-buffer + chords natively, tune the feel against real use, and only then adapt telega + and slack to the now-proven contract. The guardrail: design the contract to + the /capability floor/, not to smoke's ceiling. Smoke can do anything, which + makes it the least representative backend — validate each core leaf against + the others' known limits as it is built (telega keeps its modal root buffer; + ERC has no threads/reactions/files; slack has no file upload or search), so + the reference does not bake in something a thinner backend can never match. + Rich verbs (=r e f /=) stay optional per-backend extensions, never core. - *Phase 2 — telega.* Registration + the decision-3 cancel ladder; audit what else the minor-mode map hides in =telega-chat-mode-map=. - *Phase 3 — slack.* Per decision 5; conform compose buffers either way. @@ -196,6 +212,132 @@ Each phase ends with a manual-test checklist filed under the "Manual testing and validation" parent in =todo.org= (placement, each chord, the not-supported message), per the verification discipline. +* Global Prefix Keybinding Alphabet (per-app) + +/DRAFT addition, Claude 2026-06-16, for Craig's review (his request: "put this/ +/in the spec and I'll review it"). Folds the per-action prefix-key analysis/ +/into the held-open spec./ + +This is the third of three keybinding surfaces, and the only one the rest of the +spec doesn't already cover. The first is the in-chat buffer chords (C-c C-c +confirm, C-c C-k cancel, C-c C-a attach) owned by =cj/messenger-mode=. The second +is the cross-app aggregate verb (decision 6's jump-to-unread, one global chord +that raises the newest unread conversation in /any/ backend). This third surface +is the per-app global prefix: each messenger hangs off =C-;= with its own second +key (=S= Slack, =M= Signal, =T= Telega, =E= ERC), and today the third key, the +action leaf, is ad hoc per app. The goal: one leaf alphabet so the same action +is the same final keystroke under every messenger. + +** The problem: the same key means different things today + +| Action | Slack (C-; S) | Signal (C-; M) | Telega (C-; T) | ERC (C-; E) | +|----------------------+---------------+----------------+----------------+-------------| +| Open a chat by name | C | m | unbound | c | +|----------------------+---------------+----------------+----------------+-------------| +| Directory: all | C | d | root buffer | b | +|----------------------+---------------+----------------+----------------+-------------| +| Directory: unread | c | none | unbound | unbound | +|----------------------+---------------+----------------+----------------+-------------| +| New DM / message | d | m | unbound | unbound | +|----------------------+---------------+----------------+----------------+-------------| +| Close this chat | unbound | none | C-x k | q | +|----------------------+---------------+----------------+----------------+-------------| +| Mark read + bury | q | none | unbound | n/a | +|----------------------+---------------+----------------+----------------+-------------| +| Connect / start | s | SPC | T = launch | C | +|----------------------+---------------+----------------+----------------+-------------| +| Disconnect / stop | S | q | Q (in-buffer) | Q | +|----------------------+---------------+----------------+----------------+-------------| + +Read down a column and the leaves are arbitrary; read across a row and they +disagree. Worse, the same letter collides on meaning: =C= opens Slack's roster +but connects an ERC server; =q= disconnects Signal, marks-read in Slack, and +parts a channel in ERC. There is no muscle-memory transfer between messengers. + +** Canonical per-app actions (spelled out) + +Daily verbs (per-conversation): open a specific chat by name; view the directory +of all chats/channels; view the directory of only unread / reply-needed chats; +message someone new; reply in a thread; close the current chat window; mark the +current chat read and bury it; jump to next / previous unread chat; add a +reaction; send an attachment; search messages. + +Session verbs (lifecycle): connect / bring online; disconnect / take offline; +open the dashboard / roster overview. + +** Proposed unified leaf alphabet + +Keep each app's second key (=S M T E=); make the third key identical across all +four so the action is the same tail-keystroke regardless of app. + +| Leaf | Action | Backends that can bind it today | +|-------+------------------------------+--------------------------------------| +| j | jump to / open a chat | Slack, Signal, Telega*, ERC | +|-------+------------------------------+--------------------------------------| +| a | directory: all chats | Slack, Signal, Telega, ERC | +|-------+------------------------------+--------------------------------------| +| u | directory: unread only | Slack, Telega*, ERC* (signel: gap) | +|-------+------------------------------+--------------------------------------| +| m | message someone new / DM | Slack, Signal, Telega, ERC* | +|-------+------------------------------+--------------------------------------| +| k | close (bury) this chat | all four (thin wrappers) | +|-------+------------------------------+--------------------------------------| +| SPC | mark read + bury | Slack, Telega* (others: gap) | +|-------+------------------------------+--------------------------------------| +| n / p | next / previous unread | Telega, ERC* (others: gap) | +|-------+------------------------------+--------------------------------------| +| r | reply in thread | Slack (others: protocol N/A) | +|-------+------------------------------+--------------------------------------| +| e | reaction / emoji | Slack, Telega (others: protocol N/A) | +|-------+------------------------------+--------------------------------------| +| f | attach file | Telega (others: protocol N/A) | +|-------+------------------------------+--------------------------------------| +| / | search | Telega in-chat (others: gap) | +|-------+------------------------------+--------------------------------------| +| C | connect / start | all four | +|-------+------------------------------+--------------------------------------| +| Q | disconnect / stop | all four | +|-------+------------------------------+--------------------------------------| + +(* = the package command exists but needs a global wrapper or a binding added.) + +The core seven (=j a u m k C Q=) are the unifiable floor: every backend can +support them (once signel's gaps are filled). The richer verbs (=r e f /=) bind +only where the protocol and package allow; which-key then shows fewer options +under a thinner backend, which is honest rather than confusing. An unsupported +leaf is simply absent under that app's prefix, the same "nil verb = not +supported" stance the registry already takes for the in-buffer chords. + +** How this rides the registry + +These leaves are the global-prefix face of the same verb set decision 6 is +already growing. jump-to-unread, jump-to-chat-picker, and mark-read-and-bury in +decision 6 map directly to =u= (or the cross-app aggregate), =j=, and =SPC= +here. The registry should carry an optional per-backend command for each leaf +(=:open=, =:roster=, =:unread=, =:message=, =:close= ...), and the library +builds each app's =C-; <key>= submap from whatever the backend registered, so a +new verb lands everywhere in one place, exactly as the in-buffer verbs do. A +backend that registers nil for a leaf gets no binding for it. + +** A caveat on visual UX (keys unify cleanly; rendering does not) + +The leaf can be identical, but "open the directory" still /looks/ different per +backend, because the packages have different display models: Slack and ERC pop a +minibuffer completing-read picker; Telega and (smoke) Signal show a persistent +roster buffer. The bottom-drawer placement rule unifies where a /chat/ lands; +it does not make Slack grow a persistent roster. Unify the keys and the action +vocabulary; accept that the roster rendering differs per backend rather than +fighting each package's design. + +** Open questions for Craig + +1. The leaf letters: are =j a u m k C Q= (+ =r e f / n p SPC=) right, or do you + want different mnemonics (=o= open, =l= list, ...)? This is the muscle-memory + commitment, so it is yours to set before any binding lands. +2. Signal parity (now in scope per Craig 2026-06-16): the smoke rebuild is the + place to hit every core leaf natively. See the smoke-first note added to the + Phases section. + * Risks - Minor-mode shadowing in telega beyond C-c C-c — Phase 2 audits the C-c diff --git a/docs/design/music-config-without-emms.org b/docs/specs/music-config-without-emms-spec.org index 929423df6..32fd67367 100644 --- a/docs/design/music-config-without-emms.org +++ b/docs/specs/music-config-without-emms-spec.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 423bc355-18d3-4e39-9e7a-f768b865d95b +:STATUS: not-started +:END: #+TITLE: Design: music-config Without EMMS #+AUTHOR: Craig Jennings #+DATE: 2026-05-15 diff --git a/docs/specs/org-faces-spec-implemented.org b/docs/specs/org-faces-spec-implemented.org new file mode 100644 index 000000000..c88559061 --- /dev/null +++ b/docs/specs/org-faces-spec-implemented.org @@ -0,0 +1,154 @@ +:PROPERTIES: +:ID: 35578114-8c29-43af-97a2-fdfea01a802e +:STATUS: implemented +:END: +#+TITLE: Org Header-Row Faces — Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-15 +#+TODO: TODO | DONE SUPERSEDED CANCELLED + +* Metadata +| Status | implemented | +|----------+----------------------------------------------------------------| +| Owner | Craig Jennings | +|----------+----------------------------------------------------------------| +| Reviewer | Craig Jennings | +|----------+----------------------------------------------------------------| +| Related | [[file:../../todo.org][todo.org: org-faces module + theme-studio app]] | +|----------+----------------------------------------------------------------| + +* Summary + +A small config module, =org-faces-config.el=, that defines named, theme-agnostic faces for org's TODO keywords and priority cookies, wires them through =org-todo-keyword-faces= and =org-priority-faces=, and a matching theme-studio app titled "org-faces" so each is an editable, previewable element. It makes the agenda header row (keyword plus priority) a per-element themeable layer that reads as clearly custom, not built-in org. + +* Problem / Context + +Per-keyword and per-priority coloring was stripped from =org-config.el= earlier, so today every keyword (TODO, DOING, …) renders in org's built-in =org-todo= / =org-done=, and every priority cookie ([#A] through [#D]) renders in the single =org-priority= face — one color for all four. There's no way to color them individually. + +The dupre theme does carry a parallel custom set (=dupre-org-todo=, =dupre-org-priority-a..d=, with =-dim= variants), but it's theme-specific and currently unwired, so it colors nothing. And theme-studio has no surface for these at all: its org-mode app exposes only the built-in =org-priority=, and the preview shows a single [#A]. + +The immediate driver is theme-testing the agenda: the header row is the densest, most-scanned part of the agenda, and right now its keyword/priority colors can't be tuned in theme-studio or distinguished from built-in org faces. The user wants each keyword and priority to be its own themeable element, in its own clearly-custom namespace, surfaced in its own theme-studio section. + +* Goals and Non-Goals + +** Goals +- Each keyword (TODO, PROJECT, DOING, WAITING, VERIFY, STALLED, DELEGATED, FAILED, DONE, CANCELLED) and each priority (A-D) gets its own named face. +- The faces live in one module (=org-faces-config.el=), in their own prefix, wired through =org-todo-keyword-faces= and =org-priority-faces=. +- theme-studio surfaces them as a dedicated "org-faces" app (own section, alongside elfeed and mu4e) with one editable row per face and a header-row preview. +- They render correctly on any theme (sensible defaults) and are overridden by the generated theme. + +** Non-Goals +- Not editing the built-in org faces — the org-mode app keeps those. +- Not a general org face overhaul; only the header-row keyword + priority set. +- Not retiring or deleting the legacy =dupre-org-*= faces from dupre-faces.el in v1 — auto-dim is only repointed away from them (decision below). + +** Scope tiers +- v1: =org-faces-config.el= (base + =-dim= faces, wiring, init load); =org-faces-*-dim= wired into auto-dim (replacing the orphaned dupre-org-* entries); the theme-studio "org-faces" app (faces, preview, seeds). +- Out of scope: built-in org faces; non-keyword/priority org elements; terminal/document-rendered color sources. +- vNext (log to todo.org): retire or migrate the legacy =dupre-org-*= faces in dupre-faces.el; a grouped-subsection preview. + +* Design + +A new bespoke app, "org-faces", joins theme-studio's application list next to elfeed and mu4e. Its faces are the config's custom header-row set, not org's built-ins, and the =org-faces-= prefix says so on every row. + +** For the user + +Pick "org-faces" in theme-studio's application dropdown. The face table lists each keyword and each priority as a normal editable row — foreground, background, style toggles, box, lock — exactly like every other package face. The preview renders agenda-style lines: each keyword shown in its own face, then a =[#A] [#B] [#C] [#D]= row in the four priority faces, so the ramp is visible and any cookie is click-to-select. Setting a color there writes it into the generated theme; once that theme loads, the real agenda's keywords and priorities pick it up. Because the faces are named =org-faces-todo=, =org-faces-priority-a=, and so on, it's obvious this is the config's layer rather than built-in org. + +** For the implementer + +=org-faces-config.el= defines one =defface= per keyword and per priority (=org-faces-todo= … =org-faces-cancelled=, =org-faces-priority-a= … =org-faces-priority-d=), each with a default foreground so the row is colored out of the box. It then sets =org-todo-keyword-faces= to map each keyword string to its face and =org-priority-faces= to map =?A..?D= to the priority faces. The module is required after org so the faces exist before org applies the maps; the =defface=s themselves load eagerly, which is what org needs. + +theme-studio side, all mechanical against the existing bespoke-app machinery: +- =face_data.py=: =ORGFACES_FACES= (the face-name list) and =ORGFACES_SEED= (default colors mirroring =org-faces-config.el=). +- =generate.py=: one row in the =_BESPOKE_APPS= spec, =("org-faces","org-faces","orgfaces",ORGFACES_FACES,"org-faces-",ORGFACES_SEED)=. +- =app_inventory.py=: add =org-faces= to =BESPOKE_APPS=. +- =app.js=: =renderOrgFacesPreview= building the keyword lines and the priority-cookie row with =os('org-faces', face, text)=, registered under =orgfaces= in =PACKAGE_PREVIEWS=. +- =build-theme.el= needs no change — the package tier already emits these faces. + +The =org-faces-= prefix is also theme-studio's label-strip prefix, so rows read as "todo", "priority a", etc. + +* Alternatives Considered + +** Reuse the existing dupre-org-* names +- Good, because no new faces are defined. +- Bad, because the names are theme-specific (dupre) and the goal is a theme-agnostic, clearly-custom namespace; they'd still need all the theme-studio wiring. +- Neutral, because the auto-dim mappings already reference them, which both helps (dim variants exist) and hurts (couples the new layer to one theme). + +** Inline specs in org-todo-keyword-faces (no named faces) +- Good, because it's the least code and needs no defface. +- Bad, because theme-studio can only theme *named* faces; inline specs can't be edited or previewed there, which defeats the whole point. +- Neutral, because org supports both forms equally at runtime. + +** Put these in the existing org-mode app rather than a new app +- Good, because one fewer app in the dropdown. +- Bad, because it blurs the built-in-vs-custom line the user explicitly wants drawn. +- Neutral, because the preview would grow rather than a new one being added. + +* Decisions [4/4] + +** DONE Face prefix +- Context: the prefix is the public face namespace and theme-studio's label-strip; it must read as custom, not built-in org. +- Decision: We will use =org-faces-= (e.g. =org-faces-todo=, =org-faces-priority-a=). +- Consequences: easier — cohesive with the module and section name, and a clean label-strip prefix; harder — it still begins with "org", so a newcomer could misread it as built-in. + +** DONE defface defaults vs inherit-only +- Context: should the header row be colored on any theme, or only once a theme sets these faces? +- Decision: We will give each face a real default =:foreground= so it's colored out of the box, overridable by the theme. +- Consequences: easier — works on stock Emacs and any theme, and the theme-studio seed matches the live default; harder — the defaults are theme-blind, so a theme that doesn't override them shows the generic colors rather than its own palette. + +** DONE Auto-dim dim variants +- Context: dupre defines =dupre-org-*= and auto-dim remaps them to =-dim= variants in unfocused windows; rewiring to =org-faces-*= would drop that dim treatment unless it's carried over. +- Decision: We will include =org-faces-*-dim= variants in v1 and wire them into auto-dim, replacing the now-orphaned =dupre-org-*= entries in =auto-dim-config.el=, so keywords and priorities stay legible when a window is dimmed. The legacy =dupre-org-*= faces in =dupre-faces.el= are left defined-but-unused; retiring them stays vNext. +- Consequences: easier — the dim treatment carries onto the new layer and dupre's mapping stops referencing orphaned faces; harder — doubles the face count (base + dim), touches =auto-dim-config.el=, and under the dupre theme the new faces use their defaults until dupre themes =org-faces-*=. + +** DONE Keyword coverage +- Context: the vocabulary has 10 keywords; dupre only ever defined faces for 8. +- Decision: We will give all 10 keywords (including DELEGATED and CANCELLED) their own face. +- Consequences: easier — full control, no surprise fallbacks; harder — two more faces to seed and maintain. + +* Implementation phases + +** Phase 1 — org-faces.el module +Define the base and =-dim= =defface=s (all 10 keywords + A-D, each with real default foregrounds) and the =org-todo-keyword-faces= / =org-priority-faces= wiring, require it after org. Done when the agenda colors each keyword and priority through the new base faces. Verify with a full Emacs launch (the wiring is :config-adjacent). + +** Phase 2 — auto-dim integration +In =auto-dim-config.el=, replace the =dupre-org-*= → =dupre-org-*-dim= entries with =org-faces-*= → =org-faces-*-dim=, so dimmed windows render the new layer through its dim variants. Done when an unfocused window shows keywords/priorities in their dim colors. + +** Phase 3 — theme-studio org-faces app +Add the =face_data.py= face list and seed (base + dim), the =generate.py= spec row, the =app_inventory.py= entry, and the =app.js= preview plus registration. Done when "org-faces" appears in the dropdown next to elfeed/mu4e, the rows edit, the preview renders, and =make theme-studio-test= is green. + +** Phase 4 — generated-theme round-trip +Set a color for an =org-faces-*= face in theme-studio, =make deploy-wip=, and confirm the real agenda picks it up. Done when the round-trip lands the color in Emacs. + +* Acceptance criteria +- [ ] Each keyword (TODO … CANCELLED) renders in a distinct =org-faces-*= face in the agenda. +- [ ] [#A]-[#D] render in distinct =org-faces-priority-*= faces. +- [ ] theme-studio shows an "org-faces" app beside elfeed/mu4e, one row per face, with a header-row preview. +- [ ] A color set in theme-studio for an =org-faces-*= face appears on the real agenda after =deploy-wip=. +- [ ] =org-faces-config.el= byte-compiles clean and the theme-studio suite is green. + +* Readiness dimensions +- Data model & ownership: faces are user-authored defaults (=org-faces-config.el=) overridden by the generated theme; theme-studio edits the package-tier specs; =org-todo-keyword-faces= / =org-priority-faces= own the keyword/priority wiring. +- Errors, empty states & failure: a keyword with no mapping falls back to org's built-in =org-todo= / =org-done= — acceptable, not silent data loss. N/A otherwise (no I/O). +- Security & privacy: N/A — faces only. +- Observability: the live agenda and the theme-studio preview are the visible surface; a wrong color is self-evident. +- Performance & scale: N/A — about a dozen faces. +- Reuse & lost opportunities: rides org's built-in keyword/priority face hooks and build-theme's existing package tier; no converter changes. +- Architecture fit & weak points: same bespoke-app pattern as elfeed/mu4e. Weak point is load order — faces must exist before org applies the map; eager =defface= at module load covers it. +- Config surface: =org-todo-keyword-faces=, =org-priority-faces= (set by the module), plus the faces themselves; all overridable. +- Documentation plan: a header comment in =org-faces-config.el= and this spec; no user-facing docs needed. +- Dev tooling: existing =make theme-studio-test= and =make deploy-wip= cover build and round-trip. +- Rollout, compatibility & rollback: additive; rollback is removing the module and the theme-studio app. It re-introduces keyword/priority coloring that was deliberately stripped earlier, now as a named themeable layer. +- External APIs & deps: =org-todo-keyword-faces= and =org-priority-faces= are standard org defcustoms (verified); build-theme's package tier already emits inherit/box/style (verified this session). + +* Risks, Rabbit Holes, and Drawbacks +- Poor default colors fight unset themes. Dodge: seed conservatively, lean on theme override. +- Load order: the faces must be defined before org renders the agenda. Dodge: eager defface, require before/with org. +- Scope creep into the dupre/auto-dim migration. Dodge: it's explicitly vNext with its own decision. + +* Review and iteration history +** 2026-06-15 Mon @ 01:51:57 -0500 — Craig — author +- What: initial draft. +- Why: the A-D priority request generalized into a clearly-custom, theme-studio-surfaced header-row face layer; the prefix, default-color policy, and dupre/auto-dim reconciliation are real trade-offs worth settling on paper first. +- Artifacts: [[file:../../todo.org][todo.org org-faces task]]; theme-studio bespoke-app machinery (face_data.py, generate.py, app.js). diff --git a/docs/design/signal-client.org b/docs/specs/signal-client-spec-doing.org index ef946b80c..beee0acf1 100644 --- a/docs/design/signal-client.org +++ b/docs/specs/signal-client-spec-doing.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 0cabd6ee-c458-47b5-a8af-3ee054b25821 +:STATUS: doing +:END: #+TITLE: Design: Signal client in Emacs (forked signel) #+DATE: 2026-05-26 #+STATUS: Draft diff --git a/docs/design/theme-studio-package-faces-spec.org b/docs/specs/theme-studio-package-faces-spec-doing.org index 7f00b3279..566f34db0 100644 --- a/docs/design/theme-studio-package-faces-spec.org +++ b/docs/specs/theme-studio-package-faces-spec-doing.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 8f37a1fd-cfd3-4b25-92e5-772468092bdc +:STATUS: doing +:END: #+TITLE: theme-studio — package faces (tier 3), starting with org-mode #+AUTHOR: Craig Jennings #+DATE: 2026-06-07 diff --git a/docs/specs/theme-studio-palette-generator-spec-doing.org b/docs/specs/theme-studio-palette-generator-spec-doing.org new file mode 100644 index 000000000..b98e10789 --- /dev/null +++ b/docs/specs/theme-studio-palette-generator-spec-doing.org @@ -0,0 +1,298 @@ +:PROPERTIES: +:ID: 2df157b8-c7c1-47a9-b080-d9586c6f424c +:STATUS: doing +:END: +#+TITLE: Theme Studio Palette Generator -- Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-14 +#+TODO: TODO | DONE SUPERSEDED CANCELLED + +* Metadata +| Status | doing | +|----------+-------| +| Owner | Craig | +|----------+-------| +| Reviewer | Craig | +|----------+-------| +| Related | [[file:../../todo.org::*theme-studio palette generator][theme-studio palette generator task]] | +|----------+-------| + +* Summary +Theme Studio should grow a constraint-first palette generator that creates preview palette columns from the current theme context, then lets the user explicitly commit individual colors or whole columns. It should reuse the existing color selector as the single-color workbench instead of adding a second picker. + +The v1 feature generates palette columns only. It does not assign faces automatically; once generated colors are applied, they become normal editable palette colors with stable column ids. + +* Problem / Context +Theme Studio now has stable color columns, span controls, OKLCH editing, contrast metrics, DeltaE warnings, locks, package faces, and a live preview. The missing workflow is generating a coherent set of candidate base colors without manually choosing each hue, checking contrast, spanning, and then adjusting by eye. + +Generic palette generators are not a good fit by themselves. They optimize visual harmony before text readability, while Emacs themes need dense syntax colors, UI overlays, selections, search hits, diagnostics, and package faces to remain legible over a fixed ground. + +There is also UI overlap with the existing color selector. The selector already edits one active color, shows a swatch, drives the hex field and OKLCH picker, and adds or updates palette colors. A generator should feed that selector, not duplicate it. + +* Goals and Non-Goals +** Goals +- Generate candidate palette columns from explicit source modes and current theme constraints. +- Default to OKLCH generation so lightness and chroma are predictable. +- Default to a syntax-balanced scheme designed for readable code themes. +- Preview generated columns without mutating the real palette. +- Reuse the current color selector to inspect and tune one generated tile at a time. +- Allow adding a generated tile as a new base color column. +- Allow appending a whole generated column. +- Preserve stable column ids and existing assignments where possible when applying proposals. +- Expose diagnostics for contrast, DeltaE, gamut clamp, and rejected candidates. + +** Non-Goals +- Automatically assign generated colors to syntax/UI/package faces in v1. +- Replace the existing manual picker, swatch, or per-column span controls. +- Import external palettes, CSS colors, image colors, or theme files. Import organization remains separate. +- Generate terminal/ANSI palettes in v1. +- Add OKHSL/OKHSV generation modes in v1. +- Rewrite bg/fg automatically in v1. + +** Scope tiers +- v1: generator panel, pure planner, preview columns, existing color selector integration, add generated tile as base column, append whole generated column, diagnostics, tests, README docs. +- Out of scope: automatic face assignment, external imports, image extraction, terminal colors, rewriting bg/fg. +- vNext: replace selected columns, regenerate selected spans, regenerate generator-owned columns, OKHSL/OKHSV controls, whole-palette harmonization, CVD-aware scoring, named style presets, terminal palette derivation. + +* Design +The generator is a panel above the real palette columns. It contains generation controls and a preview area, but no separate color picker. The existing color selector remains the place where one color is inspected, tuned, named, and committed. + +For the user, the workflow is: + +1. Choose a generator source mode and scheme from compact controls in the generator panel. +2. Click preview. +3. Inspect temporary generated columns above the real palette. +4. Click a generated tile to load it into the existing color selector. +5. Optionally tune that color with the existing hex/OKLCH picker. +6. Commit either one generated tile as a new base column, or a whole generated column as normal palette entries. + +The preview layer is separate from =PALETTE=. Rendering preview columns must not create real palette entries, move existing columns, or change assignments. Preview tiles should look like palette tiles, but have a distinct preview treatment such as a dashed border/header or subtle "preview" label. + +The generator controls should be compact and explicit: + +- =Source= is a segmented control in v1: =bg/fg= and, if implemented in v1, =selected=. =bg/fg= is the default. Each option has a short tooltip: =bg/fg= means "generate from the current ground/foreground constraints"; =selected= means "harmonize around selected palette columns." +- =Scheme= can be a select/dropdown because it has more choices. A scheme is the hue-placement strategy used to propose candidate accents. The choices are not final color decisions; they are starting layouts that the planner filters through contrast, lightness, chroma, gamut, and DeltaE constraints. +- The scheme dropdown should include one-line help in the option title or adjacent help text. For example: =syntax-balanced= means "spread readable code accents across the hue wheel"; =analogous= means "nearby hues"; =triadic= means "three evenly-spaced hue families"; =manual= means "use the hues entered by the user." +- =Accent count= controls how many base columns the proposal tries to generate. The default is 8, with a v1 range of 3-12. +- =Span count= controls how many generated span steps each proposed column includes. The default should be conservative, likely 0 or the current preferred span setting, with a v1 range of 0-4. + +Therefore, the number of generated columns is configurable: it is the =accent count=, subject to rejected candidates. If the user asks for 8 accents and 2 are rejected by constraints, the preview should show 6 generated columns and report 2 rejected candidates in the summary. It should not silently backfill unrelated colors unless that behavior is explicitly added later. + +The current color selector gains a third selection mode: + +- No selection: =+ add color= creates a new manual base column from the selector value. +- Real palette tile selected: =update selected= changes that palette tile or recolors its column as today. +- Generated preview tile selected: the selector shows the generated hex/name, and =+ add color= commits that generated color as a new base color column. =update selected= is disabled unless a real palette tile is selected. + +A small status label near the selector should make this state explicit: + +#+begin_example +editing: new color +editing: palette color blue +editing: generated color blue-2 +#+end_example + +Generated columns are proposed from a source mode: + +- =bg/fg only= is the v1 default. It uses the ground and foreground as constraints, plus the chosen scheme/base hue. +- =selected columns= is v1 optional if cheap; otherwise vNext. It harmonizes around columns the user explicitly selected. +- =whole palette= is vNext. It should not be automatic in v1 because imported, experimental, or throwaway colors could make generation unpredictable. + +Generation is constraint-first. The planner chooses hue candidates from the scheme, then searches for useful OKLCH lightness/chroma values that satisfy the current background, contrast target, DeltaE separation, and gamut constraints. Classical harmony schemes are input layouts, not the final authority. + +For implementers, the feature is a pure planning layer plus DOM rendering/application: + +- The planner receives current palette, ground, source mode, generator config, and locks. +- The planner returns a proposal object and never mutates global state. +- The DOM layer renders proposal columns as preview columns. +- Applying one tile or one column converts proposal members into normal palette entries using existing column-id, span, name-collision, and gone/repoint behavior. + +** Generator config +V1 config fields: + +- sourceMode: bg-fg, selected-columns +- scheme: syntax-balanced, analogous, split-complementary, triadic, tetradic, warm-cool, manual +- baseHue: degrees, used by non-manual schemes +- manualHues: list of degrees, used by manual mode +- accentCount: integer, default 8, range 3-12 +- spanCount: integer, default 0 or current preferred span, range 0-4 in v1 +- textLightnessBand: min/max OKLCH L for text accents +- chromaBias: subdued, balanced, vivid +- contrastTarget: none, WCAG AA, WCAG AAA +- deltaEMin: default to the existing palette warning threshold +- locks: respect locked columns and assignments where apply modes touch existing data + +** Proposal object +The planner returns a proposal object with the generator config, proposed columns, rejected candidates, and a summary. Each proposed column carries a stable column id, display name, base hex, member colors with offsets and clamp/metric data, and column-level diagnostics. Rejected candidates carry the attempted hue, rejection reason, and nearest conflicting column when relevant. The summary includes generated count, clamped count, rejected count, minimum contrast, and minimum DeltaE. + +This shape is intentionally close to the existing palette-column model. Preview rendering should not need a second color model. + +** Applying generated colors +The first v1 apply actions are deliberately small: + +- =Add generated tile as base column=: creates a new normal palette column from the selected preview tile. The new column id is derived from the tile name and suffixes on collision. +- =Append generated column=: adds every member of that preview column after existing real columns. Members keep a stable shared column id. +- =Clear preview=: discards proposal state without changing the real palette. + +The following apply actions are deferred unless v1 implementation is already straightforward: + +- replace selected columns +- regenerate spans only +- regenerate generator-owned columns + +When a generated tile is committed as a base column, it starts as a one-tile column. The user can then span it using the existing column span widget. This keeps the one-color action easy to understand and avoids surprising multi-tile commits from a tile-level button. + +** Display +The panel sits between the color selector row and the committed palette columns. It has: + +- source mode segmented control +- scheme segmented control or select +- base hue/manual hue controls +- accent count and span count numeric controls +- chroma bias control +- contrast target control +- preview and clear-preview buttons +- summary row: min contrast, min DeltaE, clamped count, rejected count + +Preview columns render below the controls and above committed columns. A generated tile click loads the existing selector. A generated column header click loads the column base into the selector. A column-level =append column= button commits the whole preview column. + +* Alternatives Considered +** Generic harmony wheel that writes directly into the palette +- Good, because it is familiar and visually compact. +- Bad, because it mutates real palette state before the user can inspect results, and it optimizes hue harmony before readability. +- Neutral, because a hue wheel can still be useful as an input control inside a preview-first generator. + +** Separate generator-specific color picker +- Good, because preview tuning could be isolated from committed palette editing. +- Bad, because Theme Studio already has a capable single-color selector, and a second picker would duplicate hex, OKLCH, contrast, swatch, and add/update semantics. +- Neutral, because a future advanced generator could add a small detail panel, but v1 should not. + +** Generate from the whole palette by default +- Good, because it can harmonize with everything already on screen. +- Bad, because the palette may contain experiments, imported colors, temporary colors, or intentionally clashing accents; using all of them makes results hard to predict. +- Neutral, because whole-palette harmonization is valuable as an explicit vNext mode. + +** Full automatic face assignment +- Good, because it could produce a near-complete theme in one action. +- Bad, because it crosses into seeding, locks, role maps, and package face behavior that already have separate product decisions. +- Neutral, because the palette generator can feed a later seeding workflow. + +** Add OKHSL/OKHSV now +- Good, because those controls may feel friendlier than raw OKLCH. +- Bad, because v1 already has OKLCH math and the feature risk is workflow/state, not another color model. +- Neutral, because OKHSL/OKHSV remain good vNext candidates. + +* Decisions [5/5] +** DONE Default to preview-first generation +- Context: Generator output can disrupt a carefully tuned palette if it mutates immediately. +- Decision: We will render generated colors as temporary preview columns and require explicit add/append actions. +- Consequences: Easier to inspect and avoid accidental data loss; harder because the UI needs proposal state and apply semantics. + +** DONE Reuse the current color selector +- Context: The existing selector already edits one color, shows metrics, opens the picker, and adds/updates palette colors. +- Decision: We will make generated tile clicks load the existing selector instead of adding a second generator picker. +- Consequences: Easier to keep editing behavior consistent; harder because the selector now needs clear state for new, palette, and generated selections. + +** DONE Keep v1 palette-only +- Context: Automatic assignment would touch syntax, UI, package faces, locks, and seeding rules. +- Decision: We will generate palette columns only in v1 and leave face assignment to existing/manual workflows. +- Consequences: Easier to ship a focused generator; harder because the user still maps colors onto faces. + +** DONE Default generation source to bg/fg only +- Context: Existing palette colors may be experimental or imported; using all of them by default makes generation unpredictable. +- Decision: We will default to bg/fg constraints plus explicit scheme/base hue. Selected-column source can be included if scoped; whole-palette source is vNext. +- Consequences: Easier to understand why a proposal was generated; harder because matching an existing palette requires an explicit source mode. + +** DONE Defer OKHSL/OKHSV to vNext +- Context: OKHSL/OKHSV may be friendlier interaction models, but OKLCH already supports the required perceptual generation math. +- Decision: We will ship OKLCH generation first and consider OKHSL/OKHSV after v1 is usable. +- Consequences: Easier to keep v1 small and rigorous; harder because some users may find OKLCH controls less familiar. + +* Implementation phases +** Phase 1 -- Planner core +Add pure generator functions in =app-core.js= or a new generator module. Inputs are current palette, ground, generator config, source selection, and locks. Outputs are proposal objects. Unit tests cover scheme hue placement, OKLCH candidate generation, gamut clamp reporting, name/id collision handling, and no mutation of input state. + +** Phase 2 -- Candidate scoring +Add bounded scoring/adjustment for contrast target, DeltaE separation, chroma bias, and text lightness band. Unit tests cover rejected candidates, clamped colors, low-chroma distinguishability, and deterministic output for fixed config. + +** Phase 3 -- Generator panel and preview rendering +Add the panel, controls, proposal state, preview columns, summary metrics, and clear-preview behavior. Browser gate: preview creates temporary columns without changing committed =PALETTE=. + +** Phase 4 -- Existing selector integration +Add generated-preview selection state to the color selector. Clicking a preview tile loads its hex/name. The selector status label shows generated-vs-palette-vs-new. =+ add color= commits the selected preview tile as a new one-tile base column. Browser gates cover generated tile selection and add-as-column behavior. + +** Phase 5 -- Append generated column +Add column-level append. The generated column becomes normal palette entries with one stable column id. Collisions suffix names/ids deterministically. Browser gates cover append, collision suffixing, and unchanged existing assignments. + +** Phase 6 -- Persistence and metadata +Round-trip optional generator metadata for applied columns without requiring it for normal palette behavior. Existing palettes without metadata continue to work. Browser gate extends roundtrip coverage. + +** Phase 7 -- Documentation and cleanup +Document generator controls, source modes, preview behavior, selector integration, and limits in =scripts/theme-studio/README.md=. Remove prototype code and keep =make -C scripts/theme-studio test= green. + +* Acceptance criteria +- [ ] Previewing a generated palette does not mutate committed =PALETTE=. +- [ ] Preview columns appear above committed columns and are visually distinct. +- [ ] Clicking a generated tile loads that color into the existing selector. +- [ ] The selector clearly says whether it is editing a new color, palette color, or generated color. +- [ ] =+ add color= on a selected generated tile creates a new one-tile base column. +- [ ] Appending a generated column creates normal editable palette entries with one stable column id. +- [ ] Name and column-id collisions are suffixed deterministically. +- [ ] Generated colors report clamp, contrast, and DeltaE diagnostics. +- [ ] Existing manual palette workflows still work without opening the generator panel. +- [ ] Theme Studio tests cover planner functions, preview rendering, selector integration, apply behavior, and round-trip metadata. + +* Readiness dimensions +- Data model & ownership: Proposal state is transient and generator-owned. Applied colors become normal user-owned palette entries. Optional generator metadata is advisory and must not override manual edits. +- Errors, empty states & failure: Invalid config disables preview with an inline message naming the bad field. No preview shows an empty-state line. Rejected candidates appear in the summary. Apply failures must not partially mutate committed palette state. +- Security & privacy: N/A because generation is local deterministic color math with no credentials, network calls, or private external data. +- Observability: The preview summary shows generated, clamped, rejected, min contrast, and min DeltaE. Tile titles or details expose per-color diagnostics. +- Performance & scale: Expected accent counts are 3-12 bases with 0-4 span steps. Candidate search should remain synchronous and bounded. Broader search, if added later, needs progress/cancel. +- Reuse & lost opportunities: Reuse OKLCH, gamut clamp, contrast, DeltaE, palette columns, span generation, locks, existing selector, and existing browser gates. Do not add a second color math stack or picker. +- Architecture fit & weak points: The weak point is proposal/apply state in the DOM app. Keep planner pure and DOM code limited to rendering/applying proposal objects. +- Config surface: Public knobs are source mode, scheme, base/manual hue, accent count, span count, chroma bias, contrast target, DeltaE threshold, and lightness band. Defaults favor readable dark-theme syntax. +- Documentation plan: Update the Theme Studio README with generator controls, source modes, selector integration, preview/apply behavior, and v1 limits. +- Dev tooling: Use =make -C scripts/theme-studio test= as the primary gate and =make -C scripts/theme-studio coverage= for instrumented JS/generator coverage. +- Rollout, compatibility & rollback: The generator is additive. Existing palettes load unchanged. Preview can be cleared. Applied generated columns can be deleted manually. +- External APIs & deps: N/A because v1 has no external APIs or new dependencies. + +* Risks, Rabbit Holes, and Drawbacks +- Candidate search can become an optimization rabbit hole. V1 should use deterministic bounded search around target OKLCH bands. +- "Syntax-balanced" is subjective. Keep it documented as a heuristic, not a claim of universal taste. +- Selector state can become confusing. The status label and disabled update button are required, not polish. +- Whole-palette harmonization is tempting but should wait until preview/apply basics feel good. +- Optional metadata can drift after manual edits. Treat it as advisory only. + +* Testing / Verification / Rollout +Use the existing Theme Studio test stack: + +- Node tests for planner/scoring/collision/immutability. +- Browser hash gate for preview-only non-mutation. +- Browser hash gate for generated tile -> selector state. +- Browser hash gate for add-generated-tile-as-column. +- Browser hash gate for append generated column. +- Round-trip gate for optional generator metadata. +- Manual Chrome pass on at least one dark palette and one light palette. + +* References / Appendix +- [[file:design/theme-studio-color-harmony.org][theme-studio color harmony explainer]] +- [[id:15db8ae3-fc14-49f3-9ed5-d5ff59790904][perceptual color metrics spec]] +- [[file:theme-studio-palette-ramps-spec.org][palette ramps and contrast safety spec]] +- [[file:theme-studio-palette-columns-spec.org][palette columns spec]] +- [[file:../../todo.org::*theme-studio import organization workflow needs a spec][import organization task]] + +* Review and iteration history +** 2026-06-13 Saturday @ 16:31:01 -0500 -- Craig -- author +- What: Initial draft using the spec-create workflow. +- Why: Palette generation has real design trade-offs around color space, preview/apply behavior, assignment boundaries, and how much generator state should persist. +- Artifacts: [[file:../../todo.org::*theme-studio palette generator][theme-studio palette generator task]]. + +** 2026-06-14 Sunday @ 00:44:00 -0500 -- Craig -- author +- What: Reworked the draft around preview-first generation, existing color selector integration, generated tile add-as-column behavior, and source-mode defaults. +- Why: Craig clarified the desired UX: generated colors should be inspectable/tunable through the existing selector, and committing one generated color should create a normal base column. +- Artifacts: [[file:../../todo.org::*theme-studio palette generator][theme-studio palette generator task]]. + +** 2026-06-14 Sunday @ 01:07:00 -0500 -- Craig -- author +- What: Folded Craig's inline comments into the design, clarifying source/scheme controls, guidance text, the meaning of schemes, configurable accent count, and rejected-candidate behavior. Removed the comment/source blocks. +- Why: The generator UI needed to say what the user actually sees before implementation can proceed. +- Artifacts: [[file:../../todo.org::*theme-studio palette generator][theme-studio palette generator task]]. diff --git a/docs/design/theme-studio-perceptual-color-metrics-spec.org b/docs/specs/theme-studio-perceptual-color-metrics-spec-implemented.org index 7e7dedb22..57a4c70bc 100644 --- a/docs/design/theme-studio-perceptual-color-metrics-spec.org +++ b/docs/specs/theme-studio-perceptual-color-metrics-spec-implemented.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: 15db8ae3-fc14-49f3-9ed5-d5ff59790904 +:STATUS: implemented +:END: #+TITLE: theme-studio — perceptual color metrics (OKLCH, APCA, ΔE) #+AUTHOR: Craig Jennings #+DATE: 2026-06-08 diff --git a/docs/specs/theme-studio-preview-locate-spec.org b/docs/specs/theme-studio-preview-locate-spec.org new file mode 100644 index 000000000..bb77a2248 --- /dev/null +++ b/docs/specs/theme-studio-preview-locate-spec.org @@ -0,0 +1,148 @@ +:PROPERTIES: +:ID: fbcf0e20-1328-42b4-aa36-3401509e7816 +:STATUS: not-started +:END: +#+TITLE: Theme Studio Preview Element Locate — Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-15 +#+TODO: TODO | DONE SUPERSEDED CANCELLED + +* Metadata +| Status | not-started | +|----------+----------------------------------------------------------------| +| Owner | Craig Jennings | +|----------+----------------------------------------------------------------| +| Reviewer | Craig Jennings | +|----------+----------------------------------------------------------------| +| Related | [[file:../../todo.org][todo.org: theme-studio preview locate + org-agenda app]] | +|----------+----------------------------------------------------------------| + +* Summary + +A general theme-studio preview interaction: turn every rendered preview element into a map back to the assignment that controls it. Hovering an element shows where its color comes from -- the owning app (section), the face (element), and the current value and attributes. Clicking an element whose face is assigned on the current pane flashes it and scrolls to its assignment row. Elements styled by faces that belong to other panes are not clickable; the hover still names their section and element, so the user navigates there manually. + +* Problem / Context + +Previews render representative text styled by many faces, but there is no way to go from "this rendered bit looks wrong" to "which assignment controls it." You scan the swatch grid and guess. The gap widens as previews get richer: the planned org-agenda preview renders a realistic agenda whose lines mix scheduling and deadline faces (org-agenda's own) with TODO keywords and priorities (org-faces) and tags (org-mode). A colored substring there could belong to any of three panes, and nothing tells you which, or what its current value is. + +* Goals and Non-Goals + +** Goals +- Hover any previewed element to see its section (owning app), element (face name), and current value/attributes. +- Click an element whose face is assigned on the current pane to flash it and scroll/select its assignment row. +- Leave off-pane elements non-clickable, with the hover still naming section and element so the user can navigate there. +- A face -> (owning app, value, attributes) registry built from the app/face model, read by previews for both hover content and the clickable test. +- Let previews render substrings in faces owned by other panes live from the shared state (the org-agenda agenda mock is the first to need this); those render correctly and are the hover-only, non-clickable elements. + +** Non-Goals +- No editing from the preview -- the assignment row still owns edits. +- No auto-switching panes on click; off-pane elements are deliberately inert (decided). +- No change to the face data model; the registry is derived from existing app/face assignments. +- Not a search/filter over faces; this is point-and-identify, not query. + +** Scope tiers +- v1: the face registry; hover tooltips (section + element + value/attributes) on every previewed element; click-to-row for current-pane faces; off-pane elements non-clickable; the os() preview tag that carries each element's face; one existing preview wired as the showcase. +- Out of scope: cross-pane click navigation; editing from the preview; persisting any of this. +- vNext: a "reveal in pane" affordance for off-pane elements (switch pane + scroll) if the hover-only model proves too manual. + +* Design + +** For the user +Move the pointer over any colored element in a preview. A tooltip names the section it belongs to (the app, e.g. "org-faces"), the element (the face, e.g. "org-faces-todo"), and its current value and attributes (e.g. foreground #8fbf73, bold). If that face is one you can edit on the pane you are looking at, click it: the element flashes and the pane scrolls to and highlights its assignment row, ready to tune. If the element is colored by a face from another pane -- a keyword in the agenda is owned by org-faces, a tag by org-mode -- clicking does nothing, but the tooltip has already told you which section and element to go find. Every preview becomes a legend you can interrogate. + +** For the implementer +- Registry: a derived map from face name to its owning app, current value, and resolved attributes, built from the same app/face assignment state the editor already holds. One source for hover content and the is-on-this-pane test. Rebuilt when assignments change. +- Preview tagging: the os(app, face, text) preview helper already wraps each element in a span keyed by face; extend it to also carry the face name and owning app as data attributes so hover and click can resolve without re-parsing. +- Hover: a tooltip (or info strip) that reads the registry for the hovered element's face and shows section + element + value/attributes. +- Click: if the element's face is owned by the current pane, flash the element and scroll/select its assignment row; otherwise the element is non-interactive (cursor and affordance reflect that). +- Cross-pane rendering: os() resolves a face's current color from the registry regardless of which pane owns it, so a preview can faithfully render off-pane faces (the agenda mock's keywords/priorities/tags) while marking them non-clickable. + +* Alternatives Considered + +** Click off-pane elements and auto-switch panes to their row +- Good, because one click takes you straight to any assignment. +- Bad, because it makes a click yank you out of the pane you are studying, and the cross-pane jump is disorienting in a dense preview; the hover already names where to go. +- Rejected: off-pane elements are non-clickable (decided); the hover carries the wayfinding. + +** Hover shows only the face name, not the value +- Good, because it is the least to build. +- Bad, because "which face" without "what value" still makes you go look it up; the value/attributes in the tooltip is what closes the loop. +- Rejected: hover includes value and attributes (decided). + +** Scope it to the org-agenda preview only +- Good, because no general machinery. +- Bad, because every preview has the same go-from-render-to-assignment gap; baking it into one preview means rebuilding it for the next. +- Rejected: general feature (decided); org-agenda is the first showcase. + +* Decisions [5/5] + +** DONE General feature, not org-agenda-scoped +- Context: the interaction was raised for the agenda preview but applies to every preview. +- Decision: We build it as a general theme-studio preview capability; the org-agenda app is its first showcase. +- Consequences: easier -- every preview gains it; harder -- the registry and tagging must be general, not hand-fitted to one preview. + +** DONE Click scope: current-pane faces only +- Context: a previewed element may be colored by a face owned by another pane. +- Decision: Only elements whose face is assigned on the current pane are clickable (flash + scroll to its row). Off-pane elements are non-clickable. +- Consequences: easier -- click always has a local target, no cross-pane jump; harder -- off-pane elements rely on the hover for wayfinding. + +** DONE Hover content: section + element + value/attributes +- Context: identifying a face is only half the answer; you also want its current value. +- Decision: The hover tooltip shows the owning app (section), the face (element), and the current value and attributes. +- Consequences: easier -- closes the loop without a trip to the row; harder -- the registry must resolve value/attributes, not just the name. + +** DONE Face registry as the single source +- Context: hover content and the clickable test both need face -> owning-app + value. +- Decision: A derived registry maps each face to its owning app, value, and attributes, rebuilt from the assignment state; previews read it. +- Consequences: easier -- one place feeds hover, click, and cross-pane rendering; harder -- it must stay in sync with edits. + +** DONE Cross-pane live rendering +- Context: a faithful agenda renders keywords/priorities/tags owned by other panes. +- Decision: os() resolves any face's current color from the registry, so previews render off-pane faces live; those elements are the non-clickable, hover-only ones. +- Consequences: easier -- previews read real and the agenda mock is possible; harder -- the preview must distinguish own-pane (clickable) from off-pane (hover-only) elements. + +* Implementation phases + +** Phase 1 — Face registry +Build the derived face -> (owning app, value, attributes) map from the app/face assignment state, rebuilt on change. Unit-tested against a constructed assignment model. + +** Phase 2 — Preview tagging + cross-pane resolution +Extend os() so each previewed element carries its face name and owning app, and resolves its color from the registry regardless of owning pane. Tested on a preview that references an off-pane face. + +** Phase 3 — Hover +Tooltip showing section + element + value/attributes for the hovered element, from the registry. Browser-gate tested. + +** Phase 4 — Click +Flash + scroll/select the assignment row for a current-pane element; off-pane elements non-clickable (cursor/affordance reflects it). Browser-gate tested; one existing preview wired as the showcase. + +* Acceptance criteria +- [ ] Hovering any previewed element shows its section, element, and current value/attributes. +- [ ] Clicking a current-pane element flashes it and scrolls/selects its assignment row. +- [ ] Clicking an off-pane element does nothing and is visibly non-interactive. +- [ ] A preview can render an off-pane face's real color (cross-pane resolution) and that element is hover-only. +- [ ] The registry resolves every previewed face to its owning app, and updates when an assignment changes. + +* Readiness dimensions +- Data model & ownership: the registry is derived, not authoritative; the app/face assignment state remains the source of truth. +- Errors, empty states & failure: an element whose face resolves to no owning app falls back to hover-only with an "unassigned" note rather than a dead click. +- Security & privacy: N/A -- browser-local theme editor state. +- Observability: the tooltip is the surface; a face that fails to resolve shows that in the tooltip. +- Performance & scale: the registry is rebuilt on assignment change, not per hover; hover/click read it in O(1) by face name. +- Reuse & lost opportunities: reuses the existing os() preview helper and the assignment state; every current and future preview benefits. +- Architecture fit & weak points: the registry is the new shared structure; the weak point is keeping it in sync with edits, addressed by rebuilding on change. +- Config surface: none beyond the existing theme-studio build. +- Documentation plan: the theme-studio test suite and this spec; a note in the tool's help if the interaction isn't self-evident. +- Dev tooling: existing make theme-studio-test and the browser-gate harness. +- Rollout, compatibility & rollback: additive; rollback removes the tagging and the registry. Existing previews keep working without the interaction. +- External APIs & deps: none -- browser JS plus the existing generate pipeline. + +* Risks, Rabbit Holes, and Drawbacks +- Registry staleness: a stale registry mislabels a hover. Dodge: rebuild on assignment change; derive, never cache independently. +- Tooltip noise on dense previews: hovering everything could be busy. Dodge: tooltip on deliberate hover only, not a persistent overlay. +- Distinguishing own-pane vs off-pane reliably: the click affordance depends on it. Dodge: the registry's owning-app field is the single test. + +* Review and iteration history +** 2026-06-15 Mon — Craig — author +- What: initial draft. +- Why: breaking the org-agenda faces into their own pane surfaced the need to go from a rendered preview element back to its assignment; the interaction (hover-identifies, click-locates-on-pane, off-pane-inert) is general and worth settling before the agenda preview consumes it. +- Artifacts: the org-faces app (the bespoke-app pattern); the planned org-agenda app (first consumer); os() preview helper. diff --git a/docs/design/theme-studio-seeding-engine-spec.org b/docs/specs/theme-studio-seeding-engine-spec-doing.org index bcbf43db4..baf9f5b01 100644 --- a/docs/design/theme-studio-seeding-engine-spec.org +++ b/docs/specs/theme-studio-seeding-engine-spec-doing.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: b70b37f2-37df-4c8e-ac2f-1f20d12e33dd +:STATUS: doing +:END: #+TITLE: theme-studio — seeding engine (role table to guide-correct defaults) #+AUTHOR: Craig Jennings #+DATE: 2026-06-08 @@ -313,7 +317,7 @@ response resolved; everything else was woven into the body as written. budget this engine executes. - =scripts/theme-studio/generate.py= — =CATS=, =UI_FACES=/=UIMAP=, =APPS= / =seedPkgmap=, =exportObj= (the target shape). -- =docs/design/theme-studio-perceptual-color-metrics-spec.org= — the +- =docs/specs/theme-studio-perceptual-color-metrics-spec-implemented.org= — the =colormath.js= core that v1 OKLCH shade generation uses. * Review and iteration history diff --git a/docs/specs/theme-studio-semantic-theme-architecture-spec.org b/docs/specs/theme-studio-semantic-theme-architecture-spec.org new file mode 100644 index 000000000..01ef1902c --- /dev/null +++ b/docs/specs/theme-studio-semantic-theme-architecture-spec.org @@ -0,0 +1,266 @@ +:PROPERTIES: +:ID: fe980b12-451a-4d8b-a550-d99f9ec49f45 +:STATUS: not-started +:END: +#+TITLE: Theme Studio Semantic Theme Architecture -- Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-14 +#+TODO: TODO | DONE SUPERSEDED CANCELLED + +* Metadata +| Status | not-started | +|----------+-------| +| Owner | Craig | +|----------+-------| +| Reviewer | Craig | +|----------+-------| +| Related | [[file:../../todo.org::*theme-studio semantic theme architecture][theme-studio semantic theme architecture task]] | +|----------+-------| + +* Summary +Theme Studio currently exports JSON into a flat generated Emacs theme: every face receives resolved hex colors and attributes directly. That is faithful to the current UI state, but it loses the middle layer that makes themes like Modus easy to reason about: named palette entries, semantic color roles, reusable face templates, and a thin generated wrapper. + +This spec proposes a future Theme Studio output architecture that can preserve those layers. The v1 goal is not to replace the current flat exporter immediately; it is to define the model, converter shape, migration path, and test surface needed to support a more maintainable theme system. + +* Problem / Context +Theme Studio has grown from a color picker into a full theme workbench: palette columns, syntax assignments, UI faces, package faces, generated candidates, contrast checks, locks, and a JSON-to-Emacs-theme converter. The converter currently emits direct =custom-theme-set-faces= specs. That works, but it makes the generated theme hard to maintain outside Theme Studio because the meaning of each color is already flattened away. + +The design discussion around Modus showed a different structure. Modus keeps concrete theme files small and pushes the durable logic into a shared engine: palettes define available colors, semantic mappings define what those colors mean, face specs use semantic names, and a theme constructor resolves everything when the theme loads. + +Theme Studio does not need to become Modus, but it can borrow the architecture. This would make generated themes easier to inspect, easier to customize by hand, and more able to support rules such as "comments and comment delimiters should normally share a color" without forcing every rule into the UI. + +* Goals and Non-Goals +** Goals +- Define a layered Theme Studio theme architecture: palette data, semantic role mapping, face templates, and generated theme wrapper. +- Preserve the current flat exporter as a compatibility path until the layered output is proven. +- Allow semantic roles to group multiple faces under one design decision. +- Allow advisory semantic rules that detect inconsistent mappings, such as comment and comment delimiter diverging. +- Keep the generated output loadable by normal Emacs =load-theme=. +- Make the design testable with pure converter tests and real Emacs theme-load tests. +- Make future Theme Studio features, such as face-seeding and role-aware palette generation, easier to build. + +** Non-Goals +- Do not enforce any specific semantic design rule in v1. The comment/comment-delim rule is an example of what the layer can express, not a required policy. +- Do not replace Theme Studio's current JSON format in one step. +- Do not require users to edit generated Elisp by hand. +- Do not implement a full Modus-compatible theme engine. +- Do not automatically reassign faces based on semantic rules. +- Do not remove direct per-face overrides from Theme Studio. + +** Scope tiers +- v1: Specify the layered model, add optional export fields or an intermediate converter model, generate a loadable layered theme file, preserve current flat output, add tests, document the architecture. +- Out of scope: UI for editing all semantic roles, automatic role inference from arbitrary imported themes, rule enforcement, Modus compatibility. +- vNext: Role editor UI, advisory rule panel, role-aware palette generator integration, semantic imports, user-defined rule packs, derivative theme wrappers. + +* Design +The architecture has four layers. + +Palette data is the color inventory. It answers "what named colors exist?" A Theme Studio palette entry already has most of this information: hex, display name, and stable column id. The layered exporter should preserve that as named Elisp data rather than immediately substituting every hex into every face. + +Semantic role mapping assigns design meaning to palette entries. It answers "what does this color do?" Examples include =syntax-keyword=, =syntax-comment=, =ui-region-bg=, =org-todo-fg=, and =mode-line-bg=. A role may point to a palette color, a literal hex, or another role. The important shift is that faces stop depending directly on "blue" or "#67809c"; they depend on "keyword" or "org-todo foreground". + +Face templates map Emacs faces to semantic roles and structural attributes. They answer "which faces use which roles?" A template can say =font-lock-comment-face= uses =syntax-comment=, =font-lock-comment-delimiter-face= uses =syntax-comment-delimiter=, and =org-todo= uses =org-todo-fg= plus =org-todo-bg= with bold and box attributes. Templates are where syntax, UI, and package face coverage live. + +The generated theme wrapper ties the layers together. It declares the theme, binds palette and semantic role values, expands the face templates, calls =custom-theme-set-faces= and =custom-theme-set-variables= where needed, then provides the theme. For users, the result is still a normal loadable Emacs theme. + +For the user, this does not need to introduce a new workflow immediately. Theme Studio can still export JSON and build a theme. The difference is that the generated Elisp becomes more intelligible: + +#+begin_src emacs-lisp +(defconst theme-palette + '((bg "#000000") + (fg "#e0e0e0") + (blue "#67809c"))) + +(defconst theme-semantics + '((syntax-keyword blue) + (syntax-comment fg-muted) + (ui-region-bg blue))) + +(custom-theme-set-faces + 'theme + `(font-lock-keyword-face ((,c :foreground ,syntax-keyword))) + `(region ((,c :background ,ui-region-bg)))) +#+end_src + +For implementers, the converter gains an intermediate representation: + +#+begin_src text +theme.json + -> palette model + -> semantic role model + -> face template model + -> generated theme file +#+end_src + +The current JSON can be projected into this model conservatively. If a face has no semantic role, the converter can synthesize a private role for that face, or fall back to a direct literal. That lets the layered exporter coexist with the current state format while the UI catches up. + +** Palette data +Palette data should be owned by the palette panel. It persists user-authored color names, hexes, and column ids. Generated span members remain palette entries, but the exporter may choose whether to expose every span member as a public named color or treat some as generated/private names. + +The palette layer should avoid face knowledge. It should not know that =blue= is a keyword or that =gold= is an Org title. That meaning belongs to the semantic layer. + +** Semantic role mapping +Semantic roles are owned by the theme design layer. Some roles can be generated from existing Theme Studio assignments: + +- syntax rows become roles such as =syntax-keyword= and =syntax-string=. +- UI rows become roles such as =ui-region-fg=, =ui-region-bg=, and =ui-mode-line-bg=. +- package rows become roles such as =org-todo-fg= or =magit-branch-fg= when those roles are worth naming. + +The role map should support three value kinds: + +- palette reference: =syntax-keyword -> blue= +- role reference: =syntax-comment-delimiter -> syntax-comment= +- literal: =warning-underline -> "#ff0000"= + +Role references are what make rules and shared intent useful. If comment and comment delimiter should move together, =syntax-comment-delimiter= can point at =syntax-comment=. If the user intentionally separates them, the mapping can become explicit and the advisory can report that it was intentionally diverged. + +** Semantic rules +The semantic layer can support rules because it knows relationships that the flat face table does not. A rule can be advisory, enforced, or ignored, but v1 should only define the mechanism and ship rules as documentation/test fixtures unless a specific rule is agreed later. + +Example advisory rules: + +- =syntax-comment= and =syntax-comment-delimiter= normally use the same foreground. +- =org-headline-done= should normally be no brighter than active headline faces. +- =region= with explicit foreground/background should be checked as its own pair, while bg-only region should be checked against covered text. +- warning/error/success roles should stay distinguishable by DeltaE and not only by text label. + +The comment/comment-delim rule is a note, not a v1 requirement. The spec deliberately does not mandate it because Theme Studio should preserve deliberate design choices even when they differ from the guide. + +** Face templates +Face templates should be data, not ad hoc emitted strings. They need to represent: + +- target face symbol +- foreground role or literal +- background role or literal +- inherit face or role +- structural attributes: bold, italic, underline, strike, height, box +- optional conditions such as display class, if the project later supports them + +The first implementation can keep one template table inside =build-theme.el=. Later, this can move into generated files or a shared Theme Studio runtime. + +Templates should support both semantic and direct output. This keeps migration safe: + +#+begin_src emacs-lisp +(font-lock-keyword-face :foreground syntax-keyword) +(some-face :foreground "#aabbcc") ; direct fallback +#+end_src + +** Generated theme wrapper +The wrapper is the loadable artifact. It should be self-contained unless we intentionally introduce a shared runtime. + +Two wrapper styles are viable: + +- Single-file layered output: every generated theme contains its palette, semantic map, resolver, templates, and =custom-theme-set-faces= call. +- Shared runtime plus thin generated wrapper: a common =theme-studio-theme.el= library resolves roles and expands templates, while each generated theme mostly supplies data. + +V1 should prefer single-file layered output. It is easier to test, easier to share, and avoids requiring a generated theme user to install Theme Studio runtime files. A shared runtime can be introduced later if duplication becomes painful. + +* Alternatives Considered +** Keep only the flat exporter +- Good, because it is simple, already works, and exactly reflects the current UI state. +- Bad, because it loses role intent and makes hand inspection or downstream customization harder. +- Neutral, because it should remain the compatibility and debugging baseline. + +** Generate a full Modus-style engine +- Good, because it is a proven architecture for serious Emacs themes. +- Bad, because Modus solves a broader package-quality problem than Theme Studio needs, and copying its engine would add complexity before we know the required seams. +- Neutral, because Modus remains the reference for structuring palette, semantics, and wrappers. + +** Add semantic roles only in JSON, still emit flat Elisp +- Good, because it lets Theme Studio reason about roles without changing the generated theme format. +- Bad, because users inspecting the generated theme still see only flattened direct specs. +- Neutral, because this may be a useful intermediate phase. + +** Make semantic rules mandatory +- Good, because it can preserve consistency and prevent accidental drift. +- Bad, because theme design often has deliberate exceptions, and hard rules can fight the user's eye. +- Neutral, because some rules may later become opt-in enforcement once proven. + +* Decisions [3/3] +** DONE Output layered themes without replacing flat output immediately +- Context: The current flat exporter is useful and working. The layered architecture is broader and should not block current Theme Studio iteration. +- Decision: We will add layered output as an additional converter path or mode before considering it the default. +- Consequences: Easier rollback and comparison; harder because tests must cover two output paths during the transition. + +** DONE Treat semantic rules as advisory in v1 +- Context: The semantic layer can express design relationships, but not every relationship should be forced. +- Decision: We will model rules as advisory validation data in v1 and avoid enforcement unless a separate product decision promotes a rule. +- Consequences: Easier to preserve user intent; harder because inconsistent themes can still be exported. + +** DONE Prefer self-contained generated layered themes for v1 +- Context: A shared runtime would reduce generated file size, but it creates another dependency for loading a generated theme. +- Decision: We will prefer a self-contained layered theme file in v1. +- Consequences: Easier sharing and loading; harder because each generated theme duplicates resolver/template code. + +* Implementation phases +** Phase 1 -- Intermediate model +Add a pure intermediate representation in the converter: palette entries, semantic roles, face templates, and resolved face specs. Keep existing flat output unchanged. + +** Phase 2 -- Semantic projection +Project current Theme Studio JSON into semantic roles. Start with syntax and core UI roles, then package roles only where names are clear and useful. Preserve direct literals as fallback. + +** Phase 3 -- Layered renderer +Generate a self-contained layered =*-theme.el= from the intermediate model. The generated file should still load with normal =load-theme= and should produce the same effective faces as the flat renderer for covered data. + +** Phase 4 -- Advisory rule surface +Add pure rule checks over the semantic map. Output diagnostics in tests or converter warnings first; defer UI display. + +** Phase 5 -- Tests and comparison tooling +Add tests that convert the same JSON through flat and layered paths, load both themes in isolated Emacs sessions, and compare representative effective face attributes. + +** Phase 6 -- Documentation and rollout +Document the architecture, Makefile target, and compatibility story. Keep flat output as the default until manual inspection and tests show the layered output is trustworthy. + +* Acceptance criteria +- [ ] The current flat JSON-to-theme converter still works. +- [ ] A layered converter can generate a self-contained loadable =*-theme.el=. +- [ ] The layered generated theme preserves default, syntax, UI, and package face attributes for representative fixtures. +- [ ] The layered generated file exposes palette data and semantic mappings in readable Elisp. +- [ ] Semantic role references resolve deterministically, including role-to-role references. +- [ ] Cycles in semantic role references fail with an actionable converter error. +- [ ] Advisory rules can report findings without blocking output. +- [ ] Documentation explains the four layers and how they relate to the current flat exporter. + +* Readiness dimensions +- Data model & ownership: Palette data remains owned by Theme Studio's palette model. Semantic roles are owned by the theme design layer. Face templates are owned by the converter/runtime. Generated theme wrappers are build artifacts. +- Errors, empty states & failure: Missing roles, invalid palette references, and role cycles must name the role and input file. Unknown faces should fall back to direct specs or fail only when a strict mode is explicitly requested. +- Security & privacy: N/A because theme files contain colors and face names, not credentials or private content. +- Observability: Converter output should say whether it wrote flat or layered output and list advisory rule findings. Later UI can display semantic findings. +- Performance & scale: Expected scale is hundreds to low thousands of faces. Role resolution should be linear with cycle detection; tests should include a large package-face fixture. +- Reuse & lost opportunities: Reuse =build-theme.el= parsing, face attr construction, box conversion, and Makefile targets. Do not duplicate Emacs face attribute rules. +- Architecture fit & weak points: The converter is the right first integration point because it can prove the model without changing the browser UI. Weak point: projecting semantics from flat assignments may create artificial roles; mitigate by marking generated/private roles distinctly. +- Config surface: New converter mode or Make target, likely =theme-studio-theme-layered= or =MODE=layered=. Defaults keep current flat output. +- Documentation plan: Update Theme Studio README and add a short architecture note. Cross-link this spec from the theme converter task. +- Dev tooling: Extend existing =make theme-studio-theme= or add a sibling target. Add ERT coverage for converter behavior and a comparison test between flat/layered outputs. +- Rollout, compatibility & rollback: Keep flat output as default. Layered output is opt-in until proven. Rollback is switching the Make target/mode back to flat. +- External APIs & deps: No external APIs. Emacs =custom-theme-set-faces= and =load-theme= semantics are the only runtime dependency. + +* Risks, Rabbit Holes, and Drawbacks +- Role projection may invent names that look meaningful but are only direct mappings from one face. Keep generated/private role names visibly distinct. +- A role editor UI could become a second Theme Studio. Defer UI until the converter model proves useful. +- Advisory rules can become noisy. Start with a small list, no enforcement, and clear wording. +- Self-contained output duplicates helper code. Accept that cost until sharing/runtime needs are clearer. + +* Testing / Verification / Rollout +The test surface should start in =tests/test-build-theme.el= or a sibling converter test. Required tests: + +- role reference resolution +- cycle detection +- layered output loads in Emacs +- flat/layered equivalence for representative fixture faces +- advisory rule returns findings but does not block output +- generated file remains self-contained + +Rollout should keep the current flat output path as the default and add a separate layered target. Manual verification should compare a real Theme Studio JSON through both outputs in a current Emacs session. + +* References / Appendix +- Modus Themes source: [[https://github.com/protesilaos/modus-themes][github.com/protesilaos/modus-themes]] +- Current converter: [[file:../scripts/theme-studio/build-theme.el][scripts/theme-studio/build-theme.el]] +- Current Theme Studio README: [[file:../scripts/theme-studio/README.md][scripts/theme-studio/README.md]] +- Package-face model spec: [[id:8f37a1fd-cfd3-4b25-92e5-772468092bdc][theme-studio-package-faces-spec-doing.org]] + +* Review and iteration history +** 2026-06-14 Sunday @ 14:37:00 -0500 -- Craig -- author +- What: Initial draft for a Modus-inspired layered Theme Studio output architecture. +- Why: Craig asked how palette data, semantic role mappings, face templates, and generated wrappers could work together, including whether the semantic layer can support advisory consistency rules. +- Artifacts: [[file:theme-studio-semantic-theme-architecture-spec.org][this spec]]. diff --git a/docs/specs/theme-studio-structured-output-spec.org b/docs/specs/theme-studio-structured-output-spec.org new file mode 100644 index 000000000..ad189b7eb --- /dev/null +++ b/docs/specs/theme-studio-structured-output-spec.org @@ -0,0 +1,157 @@ +:PROPERTIES: +:ID: eaac7707-ed05-43df-9e51-b17c1d672531 +:STATUS: not-started +:END: +#+TITLE: Theme-Studio Structured Theme Output — Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-06-15 +#+TODO: TODO | DONE SUPERSEDED CANCELLED + +* Metadata +| Status | not-started | +|----------+----------------------------------------------------------------| +| Owner | Craig Jennings | +|----------+----------------------------------------------------------------| +| Reviewer | Craig Jennings | +|----------+----------------------------------------------------------------| +| Related | [[file:../../todo.org][todo.org: theme-studio output + dupre retirement]] | +|----------+----------------------------------------------------------------| + +* Summary + +Replace build-theme.el's flat deftheme (literal hex on every face) with a structured two-file output: a palette file naming each color, and a theme file whose face assignments reference the palette through a binding. A hue change becomes one edit that propagates to every face on that color, the output reads meaningfully, and the same assignments paired with a different palette make a variant. The hand-authored dupre theme is retired in the same effort: it survives only as the fallback and a structural reference now that a theme-studio export (WIP) is the active theme. + +* Problem / Context + +build-theme.el converts a theme-studio theme.json into a deftheme, and it does so flat: one =custom-theme-set-faces= with a literal hex per face and no color layer above it (the converter's own header says "Do not hand-edit; re-run the converter"). It is faithful but unreadable, and a single hue change touches every face that used that color, scattered across the file. The structure that made the hand-authored dupre theme maintainable — a palette of named colors, faces referencing those names, organized by category — is discarded at generation time. + +dupre carried that structure in a three-file split (theme / palette / faces), and that structure was the value. But it is theme-specific source the user no longer wants to hand-maintain. WIP, a theme-studio export, is already the active theme (=persist/emacs-theme= reads "WIP"); dupre is now only =fallback-theme-name= and a reference. theme.json already carries a named palette — a list of =[hex name family]= triples — so the data needed to generate a structured theme already exists; only the converter throws it away. + +The driver: make generated themes inherit dupre's structural virtues in a generated-appropriate shape, and remove dupre. + +* Goals and Non-Goals + +** Goals +- build-theme emits two files: =NAME-palette.el= (named colors) and =NAME-theme.el= (deftheme entry plus assignments that reference palette names). +- A hue change is one edit in theme.json's palette, re-exported, and reaches every face on that color. +- Assignments are separable from palette, so a palette swap yields a variant theme. +- Faces are grouped and commented by tier (default, syntax, ui, packages) for readability. +- Output stays one-way generated (do-not-hand-edit banner); theme.json is canonical. +- dupre is removed: the three theme files and its test deleted, =fallback-theme-name= moved to a built-in, references and comments updated. + +** Non-Goals +- No semantic-role layer (accent/err/keyword → palette) in v1 — deferred, but the format leaves room for it. +- No OKLCH ramps, perceptual palette renaming, or auto light/dark variants. +- No change to theme-studio's editing UI. +- Not changing the theme-studio model: the palette already exists in theme.json; v1 reads it, it does not redesign it. + +** Scope tiers +- v1: build-theme two-file structured output; the palette file from theme.json's palette list; face assignments referencing palette names via a binding, one-off hexes left literal; tier organization; regenerate the active theme in the new format; retire dupre and move the fallback. +- vNext: a semantic-role layer; per-face palette-name carriage in theme-studio (preserve intent when two roles share a hex); palette-swap variant tooling. + +* Design + +** For the user +Tune in theme-studio and export, as today. The generated theme is now two files. The palette file lists every named color once. The theme file maps each face to a color by name, grouped by area (syntax, UI, packages) so it reads like a description of the theme rather than a hex dump. To shift a hue, change it in theme-studio and re-export; every face on that color moves together. The same theme file paired with a different palette file is a variant — the lineage that took distinguished to dupre, made explicit. + +** For the implementer +build-theme/--render splits into two emitters fed by the parsed theme.json: +- Palette emitter: from theme.json's =palette= list of =[hex name family]=, write =NAME-palette.el= — a =defconst NAME-palette= (or a set of named constants) mapping name to hex, optionally grouped by family with section comments, ending in =(provide 'NAME-palette)=. +- Theme emitter: write =NAME-theme.el= — the =deftheme=, =(require 'NAME-palette)=, then =custom-theme-set-faces= wrapped in a binding over the palette names (a =let= built from the palette, mirroring dupre-with-colors) so face specs reference names. Each face's stored hex is reverse-mapped to a palette name by exact match; a hex absent from the palette stays a literal string. Faces grouped by tier with comments. End =(provide-theme 'NAME)=. +Both files carry the generated/do-not-hand-edit banner. =NAME-theme.el= requires =NAME-palette.el=, so the themes directory must be on the load path at theme-load time (the existing dupre arrangement already does this for the themes dir). + +* Alternatives Considered + +** Keep the flat per-face-hex output +- Good: no converter change; the output is trivially correct. +- Bad: unreadable, and a hue change is scattered across every face — the maintainability problem this spec exists to fix. +- Neutral: it is generated, so "unreadable" matters only when a human reads or hand-tweaks it, which the structured format is meant to enable. + +** Three-file split (theme / palette / faces), exactly like dupre +- Good: maximal separation; the deftheme boilerplate is isolated. +- Bad: a generated theme's deftheme wrapper is a few lines — a third file is more ceremony than generated output needs. +- Neutral: could become warranted in vNext if the assignments file grows unwieldy. + +** Carry a palette-name reference per face in theme.json (no reverse-map) +- Good: preserves the designer's intended name even when two roles share a hex. +- Bad: a theme-studio model and export change, larger than v1; the reverse-map gets the same readable output from data that already exists. +- Neutral: the better long-term design; logged as vNext. + +* Decisions [6/6] + +** DONE Two-file output shape +- Context: an Emacs theme needs =NAME-theme.el= for discovery; the palette wants to be independently swappable. +- Decision: Two files — =NAME-palette.el= (named colors) and =NAME-theme.el= (deftheme entry plus assignments). The assignments ride with the deftheme rather than getting a third file. +- Consequences: easier — palette swaps for variants, one place to retune hues, less ceremony than dupre's three files; harder — the theme file still mixes deftheme boilerplate with assignments. + +** DONE Faces reference the palette via a binding +- Context: faces must name colors, not inline hex, for the one-edit-propagates property. +- Decision: The theme file wraps =custom-theme-set-faces= in a =let= over the palette names (mirroring dupre-with-colors) and face specs reference the names. +- Consequences: easier — readable specs, single source of color truth; harder — the converter must build the binding and reverse-map face hexes to names. + +** DONE Derive the palette layer by reverse-mapping face hex to palette names +- Context: theme.json stores resolved hex per face but already carries a named palette list (=[hex name family]=). +- Decision: build-theme reads the palette list to emit the palette file and reverse-maps each face's hex to a palette name by exact match; a hex with no palette entry stays a literal string. +- Consequences: easier — no theme-studio model change, uses data that already exists; harder — a hex shared by two intended roles collapses to one name (intent loss), which per-face name carriage would fix in vNext. + +** DONE Semantic-role layer deferred +- Context: dupre had roles (accent/err/keyword → palette) above the palette; the user may want them later. +- Decision: No role layer in v1. The format keeps the palette binding so a role binding can slot above it later without reshaping the output. +- Consequences: easier — smaller v1, fewer indirection layers to reason about; harder — role intent is not captured yet, so a role rename is a vNext addition. + +** DONE Retire dupre, move the fallback to a built-in +- Context: WIP (a theme-studio export) is already active; dupre is only =fallback-theme-name= and a reference; the fallback has no further fallback, so it must be guaranteed present. +- Decision: Delete the three dupre files and =test-dupre-theme.el=; set =fallback-theme-name= to "modus-vivendi" (built-in, always available); update the persistence/commands tests and the stale comments in auto-dim-config.el and org-config.el. +- Consequences: easier — removes hand-maintained theme source, retires the four already-failing dupre palette tests; harder — the fallback loses chosen dimming colors (acceptable for a rare last resort), and dupre's look survives only in git and in WIP's lineage. + +** DONE Generated files stay one-way; theme.json is canonical +- Context: the current converter already declares its output do-not-hand-edit. +- Decision: Both generated files keep the generated banner; hue changes and palette swaps happen in theme-studio (or by generating from another theme.json), not by editing the output. +- Consequences: easier — no source-of-truth ambiguity, regeneration is always safe; harder — a quick hand-tweak to the palette file is overwritten on the next export, so experiments route through theme-studio. + +* Implementation phases + +** Phase 1 — palette emitter +Emit =NAME-palette.el= from theme.json's palette list: name→hex constants (grouped by family with comments), =(provide 'NAME-palette)=, generated banner. Done when the palette file loads and exposes every named color. + +** Phase 2 — theme emitter with palette references +Rewrite build-theme/--render to emit =NAME-theme.el=: deftheme, require the palette, =custom-theme-set-faces= inside a =let= over the palette, face specs referencing names (reverse-mapped from hex; literals for one-offs), tier grouping and comments, =provide-theme=. Done when a theme.json round-trips to a loading theme whose faces render identically to the old flat output. Update test-build-theme.el to the two-file shape. + +** Phase 3 — regenerate the active theme +Regenerate WIP (the active theme) in the new format via deploy-wip; confirm it loads and looks unchanged in the live daemon. Done when the round-trip lands with no visible difference. + +** Phase 4 — retire dupre +Set =fallback-theme-name= to "modus-vivendi"; update test-ui-theme-commands.el and test-ui-theme-persistence.el; fix the stale comments in auto-dim-config.el and org-config.el; delete themes/dupre-theme.el, dupre-palette.el, dupre-faces.el and tests/test-dupre-theme.el. Done when the suite is green, startup uses WIP, and the fallback resolves to modus-vivendi. (Independent of Phases 1-3 — can land first since WIP is already active in the old format.) + +* Acceptance criteria +- [ ] build-theme produces =NAME-palette.el= and =NAME-theme.el= for a given theme.json. +- [ ] The generated theme loads and its faces render identically to the prior flat output for the same theme.json. +- [ ] Changing one palette color in theme.json and re-exporting updates every face that used it. +- [ ] The palette file names every distinct palette color; one-off face hexes remain literal. +- [ ] dupre's files and test are gone; startup uses WIP; =fallback-theme-name= resolves to a present theme; suite green. + +* Readiness dimensions +- Data model & ownership: theme.json (theme-studio) is canonical; the palette list is the color source; build-theme owns the generated files; both are one-way output. +- Errors, empty states & failure: a face hex absent from the palette falls back to a literal — no failure, just an unnamed color. A missing palette file fails the theme load loudly (require error) rather than silently mis-coloring. +- Security & privacy: N/A — color data only. +- Observability: the live theme and theme-studio preview are the visible surface; a wrong reverse-map shows as a wrong color. +- Performance & scale: N/A — tens of colors, ~150 faces, generated once per export. +- Reuse & lost opportunities: rides the existing palette list and build-theme tiers; sets up palette-swap variants and a future role layer. +- Architecture fit & weak points: mirrors dupre's proven palette/faces separation. Weak point is the hex→name reverse-map collapsing shared hexes — bounded by leaving one-offs literal and deferring name carriage to vNext. +- Config surface: =fallback-theme-name= changes value; the themes load-path must include the generated palette file. +- Documentation plan: the generated banner plus this spec; no user-facing docs. +- Dev tooling: existing =make theme-studio-theme=, =deploy-wip=, and the build-theme test suite cover build and round-trip. +- Rollout, compatibility & rollback: Phase 4 (dupre removal) is independent and reversible via git; Phases 1-3 change only generated output, rollback is reverting build-theme. The active theme (WIP) keeps working in the old format until regenerated. +- External APIs & deps: =deftheme=, =custom-theme-set-faces=, =provide-theme=, =custom-theme-load-path= — all standard; modus-vivendi is built in. + +* Risks, Rabbit Holes, and Drawbacks +- Reverse-map ambiguity: one hex, several intended roles, collapses to one name. Dodge: leave one-offs literal; defer per-face name carriage to vNext. +- Identical render is the bar: the structured output must produce the same face attributes as the flat output. Dodge: a converter test that diffs resolved face specs old-vs-new for a fixture theme.json. +- Load-path for the palette file: =NAME-theme.el= requiring =NAME-palette.el= needs the themes dir on the path at load time. Dodge: reuse dupre's existing arrangement. +- Scope creep into the role layer or OKLCH work. Dodge: both are explicit Non-Goals / vNext. + +* Review and iteration history +** 2026-06-15 Mon — Craig — author +- What: initial draft. +- Why: the dupre retirement turned into a question of what shape theme-studio's generated themes should take; the palette-vs-flat format, the file split, and the reverse-map approach are real trade-offs worth settling before touching build-theme.el. +- Artifacts: scripts/theme-studio/build-theme.el (current flat renderer), scripts/theme-studio/theme.json (palette list already present), themes/dupre-* (the structural reference being retired). diff --git a/docs/design/utility-consolidation.org b/docs/specs/utility-consolidation-spec-doing.org index b84283804..b0a5fe2bd 100644 --- a/docs/design/utility-consolidation.org +++ b/docs/specs/utility-consolidation-spec-doing.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: fc2e3926-b4a1-4b45-92eb-20841e13f655 +:STATUS: doing +:END: #+TITLE: Design: Consolidate Shared Utility Helpers #+AUTHOR: Craig Jennings #+DATE: 2026-05-04 @@ -6,7 +10,7 @@ Draft. Specification only. No helper extraction is part of this document. -This is the sibling project to [[file:init-load-graph.org][Untangle the init.el Load Graph]]. The load-graph +This is the sibling project to [[id:e1fd137e-e164-42f4-a658-f4d32fbe3228][Untangle the init.el Load Graph]]. The load-graph project decides when modules load and what dependencies they declare. This project decides which module should own reusable helper behavior. @@ -291,7 +295,7 @@ Worked =system-lib.el= header: ;; Private helpers rename without alias when all call sites change in the ;; same commit. ;; -;; See also: docs/design/utility-consolidation.org for design rationale. +;; See also: docs/specs/utility-consolidation-spec-doing.org for design rationale. ;; ;;; Code: #+end_src @@ -328,7 +332,7 @@ Load shape: - =cj-cache.el= follows the first real cache consumer's layer, likely Layer 2 if modeline/agenda/refile remain eager or near-eager. - Coordinate every new topic library with - [[file:init-load-graph.org][init-load-graph.org]] before migrating its first consumer. + [[id:e1fd137e-e164-42f4-a658-f4d32fbe3228][init-load-graph-spec-doing.org]] before migrating its first consumer. * Naming Rules @@ -781,7 +785,7 @@ Recommendation: design addendum proves the API can drive the alignment. - Then decide whether modeline's buffer-local cache can use the same library or should remain specialized. -- Phase 5 step 1 produces =docs/design/cache-helper-design.org=. Until that +- Phase 5 step 1 produces =docs/specs/cache-helper-design-spec-implemented.org=. Until that file exists, =cj-cache.el= must not be created. The addendum is the prerequisite for any cache extraction commit. @@ -902,7 +906,7 @@ Inventory artifact: - Treat the inventory as living documentation. Cleared high-priority candidates may move to Phase 2 before the whole inventory is complete. - This inventory is independent from the module-shape inventory maintained by - [[file:init-load-graph.org][init-load-graph.org]]. The two projects may walk the same files, but they + [[id:e1fd137e-e164-42f4-a658-f4d32fbe3228][init-load-graph-spec-doing.org]]. The two projects may walk the same files, but they record different facts in separate artifacts. For each helper record: diff --git a/docs/design/vterm-to-ghostel-migration-spec.org b/docs/specs/vterm-to-ghostel-migration-spec-implemented.org index 5974445ad..1be4fe227 100644 --- a/docs/design/vterm-to-ghostel-migration-spec.org +++ b/docs/specs/vterm-to-ghostel-migration-spec-implemented.org @@ -1,3 +1,7 @@ +:PROPERTIES: +:ID: b54c94a0-d762-4b41-afd7-cf5593ce6675 +:STATUS: implemented +:END: #+TITLE: Migration: vterm → ghostel (single terminal engine) #+AUTHOR: Craig Jennings #+DATE: 2026-06-04 @@ -171,7 +175,7 @@ Audited file set. ** Docs (active references only — historical notes stay) - =todo.org= current task link (already updated to this -spec path). -- =docs/design/module-inventory.org=, =docs/design/init-load-graph.org= — +- =docs/design/module-inventory.org=, =docs/specs/init-load-graph-spec-doing.org= — update active =vterm-config= / =ai-vterm= references to the new names. ** Tests (~35 files) diff --git a/docs/theme-studio-palette-generator-spec.org b/docs/theme-studio-palette-generator-spec.org deleted file mode 100644 index b4814706a..000000000 --- a/docs/theme-studio-palette-generator-spec.org +++ /dev/null @@ -1,241 +0,0 @@ -#+TITLE: Theme Studio Palette Generator -- Spec -#+AUTHOR: Codex -#+DATE: 2026-06-13 -#+TODO: TODO | DONE SUPERSEDED CANCELLED - -* Metadata -| Status | draft | -|----------+-------| -| Owner | Codex | -|----------+-------| -| Reviewer | Craig | -|----------+-------| -| Related | [[file:../todo.org::*theme-studio palette generator][theme-studio palette generator task]] | -|----------+-------| - -* Summary -Theme Studio needs a palette generator designed for dense Emacs themes, not a generic graphic-design palette toy. It should start from bg/fg, generate editable color columns in OKLCH, constrain candidate colors for readable text and UI use, and let the user preview/apply changes without losing existing assignments. - -The generator is a panel over the existing palette-column model. It proposes columns, spans, and lightness/chroma bands; the user chooses whether to append, replace, or regenerate selected unlocked columns. - -* Problem / Context -Theme Studio now has stable palette columns, spans, contrast metrics, OKLCH editing, and default face inventories. That gives enough substrate to generate palettes, but the workflow is still manual: each accent must be chosen, spanned, checked for contrast, checked for distinguishability, assigned, and then adjusted by eye. - -Most palette generators optimize for attractive swatches, posters, or branding. Emacs themes have different constraints. The colors are mostly foreground text over a fixed dark or light ground, often shown in dense code, with UI backgrounds, selections, diffs, search hits, diagnostics, and package faces layered on top. A pretty palette can still be unusable if several accents collapse in low chroma, miss the bg contrast target, or produce harsh UI tints. - -The generator should therefore treat color theory as a candidate source, then filter candidates through Theme Studio's theme-specific constraints. - -* Goals and Non-Goals -** Goals -- Generate editable palette columns from bg/fg, a base hue, an accent count, span count, chroma controls, and contrast targets. -- Keep OKLCH as the default generation space so lightness and chroma behave perceptually. -- Offer harmony modes that are useful for syntax themes: syntax-balanced, analogous, split-complementary, triadic, tetradic, warm/cool balanced, and manual hues. -- Preview proposed palettes before applying them. -- Apply proposals in scoped ways: append as new columns, replace selected columns, regenerate spans only, or regenerate unlocked generator-owned columns. -- Preserve stable column ids and existing assignments where possible. -- Expose enough metrics to explain why generated colors were clamped, muted, rejected, or adjusted. - -** Non-Goals -- Automatically assign every face in the theme. Seeding and advisory features remain separate. -- Import palettes from external files. Import organization has its own spec/task. -- Replace the existing manual picker or per-column span controls. -- Generate terminal/ANSI palettes in v1. -- Support advanced appearance models like CAM16-UCS or Jzazbz in v1. - -** Scope tiers -- v1: OKLCH-based generator panel, candidate preview, apply modes, generator-owned column metadata, tests, and README docs. -- Out of scope: fully automatic theme assignment, image/screenshot extraction, CSS/theme import, terminal colors. -- vNext: OKHSL/OKHSV editing/generation mode, low-contrast preset bands, CVD-aware candidate scoring, named style presets, terminal/ANSI palette derivation. - -* Design -For the user, the generator is a compact panel above the palette columns. The user sets the ground pair, picks a scheme, picks a base hue or manual hues, chooses accent count and span count, adjusts chroma/style bias, chooses a contrast target, then clicks preview. The preview renders proposed columns as temporary strips with metric badges. Applying the preview either appends columns, replaces selected columns, regenerates spans on selected columns, or updates generator-owned unlocked columns. - -For the implementer, the generator is a pure planning layer plus a thin DOM panel. The planner accepts the current palette state and a generator config, returns a proposal object, and never mutates global state. The proposal contains column plans with stable ids, base hexes, generated member hexes, names, clamp flags, contrast/readability diagnostics, and rejected candidate notes. The apply step converts an accepted proposal into the existing palette-column entries and uses the existing repoint behavior where a column is being replaced. - -The generator should use OKLCH for v1. It chooses hue positions from a scheme, then finds a useful lightness/chroma pair for each accent against the current bg. A "syntax-balanced" mode should be the default because it matches the product better than classical harmony. It spaces hues around the wheel, but it keeps colors in a text-safe lightness band and reduces chroma when needed to preserve readability and distinguishability. - -The panel should not feel like a separate app. The proposed columns should look like columns, with the same span direction and tile affordances as real palette columns, but visually marked as a preview. The user should be able to click a proposed tile to inspect it in the picker before applying. - -** Generator config -The v1 config fields: - -- scheme: syntax-balanced, analogous, split-complementary, triadic, tetradic, warm-cool, manual -- baseHue: degrees, used by non-manual schemes -- manualHues: list of degrees, used by manual mode -- accentCount: integer, default 8, range 3-12 -- spanCount: integer, default current column span default, range 0-4 -- textLightnessBand: min/max OKLCH L for text accents -- uiTintLightnessBand: min/max OKLCH L for background/highlight tints -- chroma: global target chroma -- chromaBias: subdued, balanced, vivid -- contrastTarget: none, WCAG AA, WCAG AAA, APCA target -- deltaEMin: default to the existing palette warning threshold unless overridden -- locks: respect locked columns, respect locked assignments - -** Proposal object -The planner returns: - -#+begin_src js -{ - config, - columns: [ - { - columnId, - name, - baseHex, - members: [{ hex, name, offset, clamped, metrics }], - diagnostics: [{ level, message }] - } - ], - rejected: [{ hue, reason, nearestColumnId }], - summary: { generated, clamped, rejected, minContrast, minDeltaE } -} -#+end_src - -This shape is intentionally close to existing column/ramp data. It should be easy to unit test and easy to render as a preview. - -** Display -The generator panel sits between the palette controls and the real color columns. It has: - -- scheme segmented control -- base hue control -- accent count and span count numeric controls -- chroma/style controls -- contrast target control -- preview, append, replace selected, regenerate spans, and clear preview buttons -- summary row showing min contrast, min DeltaE, clamped count, and rejected count - -The preview renders as temporary columns. They are visually distinct from committed columns but use the same tile size, naming, and lightness order. Applying the preview re-renders the real palette and clears the preview. - -** Apply modes -- Append: add all proposed columns after existing columns, suffixing names/ids on collision. -- Replace selected: replace selected normal columns one-for-one by visual order; extra proposed columns append, extra selected columns are removed only after confirmation. -- Regenerate spans only: keep selected column ids and base colors, update span count/knobs. -- Regenerate generator-owned: update only columns marked as generator-owned and unlocked. - -The ground column remains pinned. The generator may read bg/fg and recommend ground-tint spans, but v1 does not rewrite bg/fg unless the user explicitly includes that later. - -** Persistence -Generated columns become normal palette columns after apply, but they carry optional metadata: - -#+begin_src js -{ source: "generator", generator: { scheme, version, generatedAt } } -#+end_src - -The metadata is advisory. Editing or renaming a generated column should not break the palette. A later regenerate-generator-owned action can use the metadata, but normal manual editing always wins. - -* Alternatives Considered -** Drag a generic harmony wheel into the palette -- Good, because it is familiar from design tools and visually appealing. -- Bad, because it optimizes hue relationships before text readability, which is backwards for Theme Studio. -- Neutral, because a hue wheel can still be a useful input control inside a constraint-first generator. - -** Classical palette generator only -- Good, because analogous/complementary/triadic/tetradic modes are easy to explain. -- Bad, because they do not know about bg/fg, contrast, syntax density, UI tints, or low-chroma distinguishability. -- Neutral, because those modes are still useful as candidate hue layouts. - -** Full automatic theme seeding -- Good, because it could produce a near-complete theme in one action. -- Bad, because it crosses into role assignment, package defaults, and guide-support behavior that already have separate tasks/specs. -- Neutral, because the palette generator can become one input to the seeding engine later. - -** Add many color spaces now -- Good, because OKHSL/OKHSV may be friendlier than raw OKLCH sliders for some users. -- Bad, because v1 already has the right perceptual foundation, and extra spaces would increase UI and test surface before the generation workflow is proven. -- Neutral, because OKHSL/OKHSV are good vNext candidates. - -* Decisions [0/4] -** TODO Default to syntax-balanced OKLCH generation -- Owner / by-when: Craig / spec review -- Context: Generic harmony modes produce attractive swatches but do not optimize for readable code text. -- Decision: We will make syntax-balanced the default scheme and OKLCH the default generation space. -- Consequences: Easier to generate useful Emacs themes first; harder to present the feature as a conventional color-wheel generator. - -** TODO Keep generation separate from face assignment -- Owner / by-when: Craig / spec review -- Context: Automatic assignment would touch syntax, UI, package faces, seeding, locks, and guide-support rules. -- Decision: We will generate palette columns only in v1 and leave assignment/seeding to existing or separate workflows. -- Consequences: Easier to ship a focused generator; harder because the user still must map colors onto faces. - -** TODO Use preview-first apply modes -- Owner / by-when: Craig / spec review -- Context: Generator output can disrupt a carefully tuned palette if it mutates immediately. -- Decision: We will render proposals as temporary preview columns and require an explicit append/replace/regenerate apply action. -- Consequences: Easier to inspect and avoid destructive changes; harder because the UI needs a proposal state and apply semantics. - -** TODO Defer OKHSL/OKHSV to vNext -- Owner / by-when: Craig / spec review -- Context: OKHSL/OKHSV may be friendlier interaction models, but OKLCH already supports the required perceptual generation math. -- Decision: We will ship OKLCH generation first and consider OKHSL/OKHSV after v1 is usable. -- Consequences: Easier to keep v1 small and rigorous; harder because some users may find OKLCH controls less familiar. - -* Implementation phases -** Phase 1 -- Planner core -Add pure generator functions in app-core.js or a new generator module. Inputs are current palette, ground, generator config, and locks. Outputs are proposal objects. Unit tests cover scheme hue placement, OKLCH candidate generation, gamut clamp reporting, naming/id collision handling, and no mutation of input state. - -** Phase 2 -- Candidate scoring -Add scoring and adjustment for contrast target, DeltaE separation, chroma bias, and text lightness band. Unit tests cover rejected candidates, clamped colors, low-chroma distinguishability, and deterministic output for fixed config. - -** Phase 3 -- Generator panel and preview rendering -Add the panel, controls, preview columns, summary metrics, and clear-preview behavior. Browser gate: preview creates temporary columns without changing the committed palette. - -** Phase 4 -- Apply modes -Implement append, replace selected, regenerate spans only, and regenerate generator-owned modes. Browser gates cover collision suffixing, locked column preservation, selected-column replacement, and generator-owned regeneration. - -** Phase 5 -- Persistence and import/export -Round-trip optional generator metadata without requiring it for normal palette behavior. Existing imports without metadata continue to work. Browser gate extends roundtrip coverage. - -** Phase 6 -- Documentation and cleanup -Document the generator in README and note the limits: palette only, no automatic face assignment. Remove any dead prototype code and keep make test green. - -* Acceptance criteria -- [ ] A user can preview a syntax-balanced palette from current bg/fg without mutating the real palette. -- [ ] A user can append generated columns and then edit/reorder/span them like normal columns. -- [ ] A user can replace selected columns with a generated proposal without losing locked columns. -- [ ] Generated colors report clamp, contrast, and DeltaE diagnostics. -- [ ] Export/import preserves committed generated columns and optional generator metadata. -- [ ] Existing manual palette workflows still work without opening the generator panel. -- [ ] Theme Studio tests cover planner functions, preview rendering, apply modes, and round-trip behavior. - -* Readiness dimensions -- Data model & ownership: The proposal is transient and generator-owned until applied. Applied columns become normal user-editable palette columns with optional advisory generator metadata. -- Errors, empty states & failure: Invalid config disables preview with an inline message naming the bad field. Rejected candidates appear in the summary rather than silently disappearing. Replace-selected asks for confirmation before removing unmatched selected columns. -- Security & privacy: N/A because generation is local, deterministic color math with no credentials or external requests. -- Observability: The preview summary shows generated, clamped, rejected, min contrast, and min DeltaE counts. Each tile exposes diagnostics in its title or detail panel. -- Performance & scale: Expected accent counts are small, 3-12 bases with up to 4 steps each. Candidate search should stay synchronous; if broader searches are added later, add progress/cancel. -- Reuse & lost opportunities: Reuse OKLCH, gamut clamp, ramp, contrast, APCA, DeltaE, locks, column ids, and existing browser gates. Do not invent a second color math stack. -- Architecture fit & weak points: The weak point is proposal/apply state in the DOM app. Keep planner pure and make the DOM layer only render/apply proposal objects. -- Config surface: Public knobs are scheme, hue/manual hues, accent count, span count, lightness bands, chroma bias, contrast target, DeltaE threshold, and lock behavior. Defaults should favor readable dark-theme syntax. -- Documentation plan: Update scripts/theme-studio/README.md with generator controls, apply modes, limits, and how generated columns become normal columns. -- Dev tooling: Existing scripts/theme-studio make test remains the gate. Add node tests for planner/scoring and browser hash gates for preview/apply. -- Rollout, compatibility & rollback: The generator is additive. Existing palettes load unchanged. Applied columns can be manually deleted or replaced; optional metadata can be ignored by old code. -- External APIs & deps: N/A for v1. No network or external package dependency is needed. - -* Risks, Rabbit Holes, and Drawbacks -- Candidate search can become a rabbit hole. V1 should use deterministic bounded search around target OKLCH bands, not open-ended optimization. -- "Syntax-balanced" could become subjective. Keep it documented as a default heuristic, not a claim of universal taste. -- Preview/apply modes can overcomplicate the UI. If the panel feels heavy, hide advanced knobs behind a disclosure and keep preview/apply visible. -- Optional generator metadata could drift from manual edits. Treat it as advisory only. - -* Testing / Verification / Rollout -Use the existing Theme Studio test stack: - -- Node tests for planner/scoring/collision/immutability. -- Browser hash gate for preview-only non-mutation. -- Browser hash gate for append/replace/regenerate apply modes. -- Round-trip gate for generator metadata. -- Manual Chrome pass on a dark palette and a light palette. - -* References / Appendix -- [[file:design/theme-studio-color-harmony.org][theme-studio color harmony explainer]] -- [[file:design/theme-studio-perceptual-color-metrics-spec.org][perceptual color metrics spec]] -- [[file:theme-studio-palette-ramps-spec.org][palette ramps and contrast safety spec]] -- [[file:theme-studio-palette-columns-spec.org][palette columns spec]] -- [[file:../todo.org::*theme-studio import organization workflow needs a spec][import organization task]] - -* Review and iteration history -** 2026-06-13 Saturday @ 16:31:01 -0500 -- Codex -- author -- What: Initial draft using the spec-create workflow. -- Why: Palette generation has real design trade-offs around color space, preview/apply behavior, assignment boundaries, and how much generator state should persist. -- Artifacts: [[file:../todo.org::*theme-studio palette generator][theme-studio palette generator task]]. |
