aboutsummaryrefslogtreecommitdiff
path: root/docs/design/theme-studio-seeding-engine-spec.org
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design/theme-studio-seeding-engine-spec.org')
-rw-r--r--docs/design/theme-studio-seeding-engine-spec.org350
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.