diff options
| author | Craig Jennings <c@cjennings.net> | 2026-06-15 10:24:40 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-06-15 10:24:40 -0500 |
| commit | 45e0f6e896b2c34de25d5c3aa18474c79d6a1e72 (patch) | |
| tree | 3f4d822aa5da53f4e6bbebbdc7fb400a2b212189 /docs/design/theme-studio-seeding-engine-spec.org | |
| parent | a5c9f48220cd52770f10f7627922b9fc8e2204cc (diff) | |
| download | dotemacs-45e0f6e896b2c34de25d5c3aa18474c79d6a1e72.tar.gz dotemacs-45e0f6e896b2c34de25d5c3aa18474c79d6a1e72.zip | |
docs: move specs to docs/specs/ with lifecycle-status filenames
Separate the 27 formal specs from working notes. Specs move to docs/specs/, notes stay in docs/design/. Each spec carries its lifecycle in the filename (-spec, -spec-doing, -spec-implemented, -spec-superseded) plus an authoritative ID and STATUS property drawer. The status came from checking each spec against the code, not the doc's own field: 6 implemented, 8 in progress, 12 not started, 1 superseded.
Inbound links become org-id links so future status renames don't break them; code-comment paths repoint to docs/specs/. Working notes, inventories, reviews, and brainstorms stay in docs/design/.
Diffstat (limited to 'docs/design/theme-studio-seeding-engine-spec.org')
| -rw-r--r-- | docs/design/theme-studio-seeding-engine-spec.org | 350 |
1 files changed, 0 insertions, 350 deletions
diff --git a/docs/design/theme-studio-seeding-engine-spec.org b/docs/design/theme-studio-seeding-engine-spec.org deleted file mode 100644 index bcbf43db4..000000000 --- a/docs/design/theme-studio-seeding-engine-spec.org +++ /dev/null @@ -1,350 +0,0 @@ -#+TITLE: theme-studio — seeding engine (role table to guide-correct defaults) -#+AUTHOR: Craig Jennings -#+DATE: 2026-06-08 - -* Status - -Spec / review incorporated (Codex, 2026-06-08). Turns the color-assignment guide's seed table -and shade budget into an executable seeding engine: the tool opens with every -tier (syntax, UI faces, org-mode package faces) already colored to the guide's -defaults, so the user retunes hues with the picker rather than building a theme -from blank. Also reseeds the bundled =dupre= theme to the canonical compact -mapping (it currently diverges on two roles). - -Derives directly from =scripts/theme-studio/theme-coloring-guide.org= — the seed -table (role to palette-family / weight / channel) and the Shade budget (how many -shades each hue family carries). This spec encodes that table as data, classifies -each tier's faces into roles, and applies the table to produce the defaults. - -Rubric: *Ready.* Craig answered the four open questions (folded into Agreed -decisions) and Codex's review is incorporated. One decision reshapes the plan: v1 -generates shades with OKLCH (Craig's call), reusing the perceptual-metrics -=colormath.js= core, so this feature sequences after that spec's Phase 1. Two v1 -phases, each headless-testable. - -* Background — how the tool seeds today - -=scripts/theme-studio/generate.py= holds three face inventories, each with its -own ad-hoc default source: - -- *Syntax* — =CATS=, 21 categories keyed =bg p kw bi pp fnd fnc dec ty prop con - num str esc re doc cm cmd var op punc=. Defaults come from =COLS= (in - =samples.py=) into =MAP= and =BOLD=. There is no role layer; each category - carries a hand-set color. -- *UI faces* — =UI_FACES= (20 faces) with defaults in =UIMAP=, hand-authored. - This map already follows the guide closely (state faces are background-only, - active louder than idle, error/warning/success on the conventional hues), which - is the validation that the guide's principles describe a good UI tier rather - than invent one. -- *Package faces* — =APPS[app].faces=, each row =[face, label, default-dict]=. - =seedPkgmap()= reads the per-face default-dict. About twenty bespoke packages - (org, magit, elfeed, mu4e, ghostel, dashboard, lsp-mode, flycheck, dired, - dirvish, calibredb, erc, signel, pearl, slack, telega, shr, and more) carry - curated seed colors; generic inventory packages (from =package-inventory.json=) - seed to the default foreground. - -Three problems this spec addresses: - -1. *No role layer.* Each tier's defaults are set face-by-face by hand. There is - no single place that says "definitions are the warm anchor, bold" and projects - it onto syntax, UI, and org at once. The guide now states that table; the tool - does not consume it. -2. *dupre diverges from its own guide.* The compact mapping says builtins are - blue-grey and function definitions are gold; =dupre= assigns builtins to blue - (=bi= shares =kw='s hue) and definitions to silver (=fnd=). The guide records - this as a known divergence to be reseeded. -3. *Tiers do not open guide-correct.* UI is close by luck of hand-tuning; syntax - carries dupre's divergence; org's long tail is unseeded. Opening seeded across - all three is the goal. - -* Goal - -A seeding engine with three parts and one surfacing rule: - -1. *The seed model as data* — a named palette with the shade budget, a - role-to-treatment table, and a face-to-role map per tier. The guide's table, - made executable. -2. *A =seed()= operation* — applies the role table through each tier's - face-to-role map to produce the default assignments (=MAP=/=BOLD= for syntax, - =UIMAP= for UI, =PKGMAP= defaults for packages). -3. *Reseed dupre* — regenerate =dupre-revised.json= from the engine so it matches - the compact mapping (builtins blue-grey, definitions gold). - -Surfacing rule (Craig): the tool *opens seeded*. The syntax tier is already -guide-correct on load, so the user adjusts hues with the picker, then scrolls to -the UI faces. A "reseed from guide" button restores the defaults on demand. - -Non-goals: role-mapping the non-org bespoke packages (org is the one document -package worth a role map; the other ~20 keep their existing curated =APPS= seeds, -and reseed resets them to those defaults rather than flattening them — see -Package scope); per-tier reseed controls (v1 reseeds all three owned tiers at -once). - -* The seed model - -** Palette and shade budget - -A named swatch set, one to three shades per hue family, per the guide's Shade -budget. The names are the contract. v1 *generates* the shades with OKLCH (Craig's -call): each family is anchored by a base hue (the dupre anchors — blue, gold, -regal, sage, terracotta), and its quieter or brighter shades are derived by -stepping OKLCH lightness/chroma from that anchor, using the perceptual-metrics -=colormath.js= core. Generation is a first guess; any hue that reads wrong gets a -hand-authored override swatch. Rough shape: - -- *Neutrals:* =ground= (bg), =bg-dim=, =fg=, =muted-fg=, =comment=. -- *Blue:* =blue= (keyword), =blue-grey= (builtin — blue at lower chroma/lightness). -- *Gold:* =gold= (definition), =gold-quiet= (call). -- *Violet:* =regal= (types/decorators). -- *Green:* =sage= (string), =sage-muted= (docstring), =sage-bright= (escape). -- *Teal:* =teal= (regexp). -- *Terracotta:* =terracotta= (numbers/constants). -- *Signal:* =red=, =amber=, =green=, =blue= (reused) for error/warning/success/link. - -Roughly fifteen swatches across seven or eight hues. The builtin =blue-grey= and -the call =gold-quiet= are the swatches dupre is missing today and gains on -reseed. - -** Role-to-treatment table - -The guide's seed table as data: each role maps to a swatch, a weight, an optional -slant/underline, and a channel (foreground or background). One literal object, -e.g. - -#+begin_src js -ROLES = { - base: {swatch:'fg', weight:'normal', channel:'fg'}, - structure: {swatch:'muted-fg', weight:'normal', channel:'fg'}, - control: {swatch:'blue', weight:'bold', channel:'fg'}, - builtin: {swatch:'blue-grey', weight:'normal', channel:'fg'}, - def: {swatch:'gold', weight:'bold', channel:'fg'}, - call: {swatch:'gold-quiet', weight:'normal', channel:'fg'}, - type: {swatch:'regal', weight:'normal', channel:'fg'}, - string: {swatch:'sage', weight:'normal', channel:'fg'}, - docstring: {swatch:'sage-muted', slant:'italic', channel:'fg'}, - escape: {swatch:'sage-bright',weight:'normal', channel:'fg'}, - literal: {swatch:'terracotta', weight:'normal', channel:'fg'}, - comment: {swatch:'comment', slant:'italic', channel:'fg'}, - state: {swatch:'tint', channel:'bg'}, - sig_error: {swatch:'red', channel:'fg'}, - sig_warn: {swatch:'amber', channel:'fg'}, - sig_ok: {swatch:'green', channel:'fg'}, - sig_link: {swatch:'blue', underline:true, channel:'fg'}, - heading: {swatch:'ramp', channel:'fg'}, // see heading ramp -} -#+end_src - -** Face-to-role maps - -*** Syntax (CATS key to role) - -=p=, =var= to base; =op=, =punc=, =cmd= to structure; =kw= to control; =pp= to -control (shared, optionally muted); =bi= to builtin; =fnd= to def; =fnc= to call; -=dec=, =ty=, =prop= to type; =con=, =num= to literal; =str= to string; =doc= to -docstring; =esc=, =re= to escape (=re= to a teal variant if present); =cm= to -comment; =cmd= to structure (delimiter, dimmer). =bg= is the ground, set -directly. - -*** UI faces (UI_FACES to role) - -=region=, =hl-line=, =highlight=, =show-paren-match= to state (background tint, -no fg); =isearch= to an active match chip (may invert); =lazy-highlight= to a -quieter match; =isearch-fail=, =show-paren-mismatch= to sig_error; =error= to -sig_error, =warning= to sig_warn, =success= to sig_ok; =link= to sig_link; -=mode-line= to active chrome, =mode-line-inactive=, =line-number=, =fringe=, -=vertical-border= to idle/receding chrome; =line-number-current-line= to active -chrome; =cursor= to its own; =minibuffer-prompt= to control. - -*** Org-mode (face to one of six roles) - -=org-level-1..8= to heading ramp; =org-meta-line=, =org-drawer=, -=org-special-keyword=, =org-property-value=, =org-block-begin-line= / -=org-block-end-line=, =org-ellipsis=, =org-tag=, =org-date=, -=org-document-info-keyword= to markup-recede; =org-block=, =org-code=, -=org-verbatim=, =org-inline-src-block= to code-like (reuse the syntax literal -lane); =org-todo= / imminent deadlines to sig (warm), =org-upcoming-deadline= to -sig_warn, =org-scheduled= / =org-done= to receded/cool (with =org-done= taking -strikethrough); =org-link= to sig_link; =org-quote=, =org-verse= to emphasis -(italic). The org long tail that does not classify seeds to base, as today. - -** Package scope - -The role engine owns three default sources: syntax, UI, and the *org-mode* -package faces. It does not touch the other ~20 bespoke packages in =APPS= (magit, -elfeed, mu4e, and the rest): their curated seed colors stay exactly as today, and -the reseed button *resets them to their existing =APPS= defaults* rather than -role-generating or flattening them to foreground. Generic inventory packages keep -seeding empty/default. So =seed(model)= returns =packages.org-mode= only; the -non-org defaults continue to flow from =seedPkgmap()= over the curated =APPS= -dicts, and reseed re-runs =seedPkgmap()= for them. A =#seedtest= asserts a non-org -bespoke package (e.g. magit) keeps its curated seed after open and after reseed. - -Reseeding preserves the package-face import guarantees already established by -=mergePackagesInto= / =packagesForExport= (unknown app/face preservation, old-JSON -compatibility, recoverable references to deleted palette colors); this spec does -not re-decide them. - -** Heading ramp - -=org-level-1..8= share one hue across three or four lightness steps (the guide -does not spend eight distinct shades). v1 generates the steps with OKLCH: from a -base hue, step lightness down per level (level 1 strongest and bold, deeper levels -quieter), cycling the steps past level 4. This uses the same =colormath.js= shade -generation as the palette above. - -* The seed() operation - -A pure function, =seed(model)= returns ={syntax, ui, packages}= default -assignments: - -- *syntax*: for each =CATS= key, look up its role, resolve the role's swatch to a - hex and its weight, produce =MAP[key]= and =BOLD[key]=. -- *ui*: for each =UI_FACES= face, resolve its role to =UIMAP[face]= ({fg, bg, - bold, italic, underline}), honoring the channel (state roles set bg only). -- *packages.org-mode*: for each org face, resolve its role to a default-dict - ({fg, bg, bold, italic, strike, inherit, height}). - -The output is exactly the shape =exportObj()= already emits (=assignments=, -=ui=, =packages=), so =seed()= produces a =theme.json= the existing import path -loads unchanged. =packages= carries only =org-mode= (Package scope); the non-org -curated defaults flow through =seedPkgmap()= as today. Reseeding dupre is -=seed(model)= combined with the curated package seeds, written to -=dupre-revised.json= (the canonical package-aware artifact — see Surfacing). - -* Surfacing in the tool - -- *Open seeded.* The page's initial =MAP=/=UIMAP= come from =seed(model)= (inlined - defaults), not from hand-set =COLS=/=UIMAP=; =PKGMAP= comes from =seed(model)='s - org defaults plus =seedPkgmap()= over the curated =APPS= dicts for the rest. On - load the syntax tier is guide-correct; the user retunes hues and scrolls to UI. -- *Reseed button.* A "reseed from guide" control reapplies the seeds to all three - owned tiers and resets the non-org packages to their curated =APPS= defaults. It - warns first, naming the scope: "Reseed syntax, UI, and package defaults from the - guide? This discards current color assignments." -- *Canonical artifact.* The reseeded bundle is written to =dupre-revised.json=, - the full package-aware file the README and =build-theme.el= example use. - =dupre.json= stays a legacy minimal import fixture (no =packages= key) unless - deliberately migrated. Importing the reseeded =dupre-revised.json= and opening - fresh land on the same state. - -* Implementation phases - -1. *Seed model + seed() + tests.* Add the palette anchors + OKLCH shade - generation (reusing =colormath.js=), the =ROLES= table, and the three - face-to-role maps as data in =generate.py= (or a sibling inlined like - =samples.py=); write the pure =seed()=. Gate: =#seedtest= asserts representative - faces land on the right swatch/weight/channel in each tier (=bi= blue-grey, - =fnd= gold + bold, =var= base, =op= / =punc= muted, =doc= italic; =region= / - =hl-line= bg-only, =link= underlined, =error= / =warning= / =success= on signal - hues, active vs inactive chrome differentiated; =org-level-1= strongest, - =org-code= the fixed-pitch literal lane, =org-done= receded/struck) AND that a - non-org bespoke package (e.g. magit) keeps its curated seed. -2. *Open seeded + reseed + dupre-revised regen.* Wire the initial state to - =seed(model)= (plus =seedPkgmap()= for the non-org packages); add the all-tier - reseed button with the scope-named overwrite warning, resetting non-org - packages to their =APPS= defaults; regenerate =dupre-revised.json= from the - engine. Gate: =#selftest= still PASS; a headless check that default-on-open - equals =seed(model)=; an *artifact round-trip* check that the regenerated - =dupre-revised.json= imports back to the same seeded state (package defaults and - source markers included); a Chrome eyeball that the seeded syntax tier reads as - a coherent dupre. - -Dependency: v1 reuses the perceptual-metrics =colormath.js= core for OKLCH shade -generation, so it sequences after that spec's Phase 1 (the math foundation). No -second color-math implementation. - -* vNext candidates - -- Per-tier reseed controls (reseed just syntax, just UI, just org) after the - all-at-once v1 button. -- Role-mapping selected non-org bespoke packages beyond org, if their curated - defaults prove worth regenerating from the table. -- The guide-support views and advisories already tracked in =todo.org=. - -* Acceptance criteria - -- *Phase 1*: =seed()= is pure and table-driven; representative faces in all three - tiers resolve to the guide's seed-table treatment; a non-org bespoke package - keeps its curated seed; OKLCH generation produces the family shades and the - heading ramp; =#seedtest= PASS. -- *Phase 2*: the tool opens with syntax/UI/org seeded from =seed(model)= and the - non-org packages on their curated =APPS= defaults; the reseed button restores - all three owned tiers (and resets non-org to curated defaults) behind a - scope-named warning; =dupre-revised.json= is regenerated, matches the compact - mapping (=bi= blue-grey, =fnd= gold), and round-trips back to =seed(model)= on - import; =#selftest= PASS; a Chrome eyeball confirms a coherent dupre. - -* Agreed decisions (v1) - -Answered by Craig (2026-06-08), folded in. - -1. *Palette swatch source.* Generate the shades with OKLCH and fix hues that read - wrong by hand override (Craig overrode the hand-authored recommendation). This - moves OKLCH generation into v1 and makes the feature reuse the - perceptual-metrics =colormath.js= core, sequencing after that spec's Phase 1. -2. *Heading ramp depth.* Three or four distinct lightness steps, cycled across - levels 1-8. -3. *Converter sharing.* Tool-only for v1; =build-theme.el= consumes the exported - =theme.json= regardless. -4. *Reseed scope.* All three owned tiers at once; per-tier reseed is vNext. - -* Review dispositions - -Codex's review (2026-06-08) was accepted in full. The items below note the two -findings that corrected factual errors in the draft and the one open choice this -response resolved; everything else was woven into the body as written. - -- *Corrected — package scope (high-priority finding 2).* The draft said non-org - packages "seed to the default foreground." Wrong: =APPS= carries curated seeds - for ~20 bespoke packages. Rewritten so the role engine owns only org among - packages and the rest keep their curated =APPS= defaults, with reseed resetting - to those (see Package scope). -- *Corrected — canonical artifact (high-priority finding 3).* The draft named - =dupre.json=; the package-aware bundle is =dupre-revised.json=. Replaced - throughout, with =dupre.json= noted as the legacy minimal fixture. -- *Resolved — OKLCH dependency (high-priority finding 1).* The review offered two - routes to OKLCH-in-v1 (depend on the perceptual-metrics core, or build a local - minimal helper). Chose the dependency, to avoid a second color-math - implementation. - -* Sources - -- =scripts/theme-studio/theme-coloring-guide.org= — the seed table and Shade - 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 - =colormath.js= core that v1 OKLCH shade generation uses. - -* Review and iteration history - -** 2026-06-08 Mon @ 19:00:17 -0500 — Codex — reviewer -- *What changed or was recommended:* Created the implementation-readiness review - and marked the spec =Not ready=. The review treats the inline =cj: comment= - blocks as answered product input, then flags three remaining implementation - blockers: the palette-source answer moves OKLCH generation into v1 while the - phase plan still says vNext; the package seeding scope conflicts with the - current many-bespoke-app =APPS= surface; and the spec names =dupre.json= even - though the current README/build path uses =dupre-revised.json= as the full - package-aware artifact. -- *Why:* The role-table direction is sound, but those conflicts would force the - implementer to decide dependency order, package-default preservation, and - which bundled JSON file to write. -- *Artifacts:* the review file (consumed and deleted on response; see Review dispositions). - -** 2026-06-08 Mon @ 19:06:06 -0500 — Claude Code — responder -- *What changed:* Folded Craig's four answers into Agreed decisions and accepted every Codex finding. OKLCH shade generation moved into v1 (reusing =colormath.js=, sequenced after perceptual-metrics Phase 1); package scope rewritten so the engine owns only org and the ~20 non-org bespoke packages keep their curated =APPS= seeds (reseed resets to those); =dupre.json= references replaced with =dupre-revised.json= (canonical, package-aware) and =dupre.json= noted legacy; phases collapsed to two v1 phases with a vNext section; added the non-org-preserved and artifact-round-trip tests, the scope-named reseed warning, and the import-guarantee citation. -- *Why:* The draft described an older, narrower package surface and the wrong dupre artifact; left uncorrected, an implementer could wipe curated package defaults or write the wrong file. Two of the three blockers fixed errors in my draft. Rubric Draft to Ready. -- *Artifacts:* consumed and deleted the review file; see Review dispositions and Agreed decisions. - -** 2026-06-08 Mon @ 19:11:06 -0500 — Codex — reviewer -- *What changed or was recommended:* Re-reviewed the incorporated spec against - the current generator, README, task tracking, and previous findings. Assigned - =Ready=: the OKLCH dependency, non-org package seed preservation, and - =dupre-revised.json= artifact story are now explicit. Fixed one stale - non-blocking source note that still referred to Phase 3. -- *Why:* The spec now gives an implementer a coherent v1: two phases, explicit - dependency on perceptual-metrics Phase 1, table-driven =seed()=, open-seeded - and reseed behavior, package preservation rules, artifact round-trip tests, - and vNext boundaries. -- *Artifacts:* No review file written; no blocking findings. |
