aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/README.md
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/theme-studio/README.md')
-rw-r--r--scripts/theme-studio/README.md169
1 files changed, 115 insertions, 54 deletions
diff --git a/scripts/theme-studio/README.md b/scripts/theme-studio/README.md
index caee7b24..6ca3285e 100644
--- a/scripts/theme-studio/README.md
+++ b/scripts/theme-studio/README.md
@@ -43,18 +43,28 @@ The runner regenerates the page, runs the Python templating tests
the spliced page script, and the browser hash gates in headless Chrome
(`#selftest`, `#cursortest`, `#readouttest`, `#deltatest`, `#oklchtest`,
`#planetest`, `#locktest`, `#sorttest`, `#mocktest`, `#contrasttest`,
-`#safetest`, `#healtest`, `#familytest`, `#counttest`, `#baseedittest`,
-`#roundtriptest`). It exits non-zero on any failure. The browser gates need a
+`#safetest`, `#healtest`, `#columntest`, `#counttest`, `#baseedittest`,
+`#roundtriptest`, `#beveltest`, `#previewlinktest`). It exits non-zero on any failure. The browser gates need a
Chromium-family browser; without one they report SKIPPED rather than passing
silently. The pure color math and the extracted picker logic (`planeCell`,
`paletteWarnings`) live in `colormath.js` so they are unit-tested directly in
-Node; the DOM glue is covered by the browser hash gates.
+Node; palette-column plans and lock-set plans live in `app-core.js` so edge
+cases are unit-tested directly. The DOM glue is covered by the browser hash
+gates.
## Files
-- `generate.py` — emits the HTML+JS, and embeds the package data. Edit here to
- change layout or behavior.
-- `samples.py` — the six language code samples and the default syntax
+- `generate.py` — assembles the generated page from the source JS/CSS, data, and
+ template.
+- `theme-studio.template.html` — static page shell with placeholders for the
+ inlined CSS/JS/data. Edit here for layout markup.
+- `face_data.py` — bespoke package face lists and seed defaults.
+- `palette-actions.js` — stateful palette-panel actions and rendering, inlined
+ into the generated page.
+- `browser-gates.js` — the browser hash-gate test harness, also inlined.
+- `app_inventory.py`, `face_specs.py`, `default_faces.py` — generator helpers for
+ package inventory, face-spec defaults, and captured Emacs defaults.
+- `samples.py` — the language code samples and the default syntax
category→color map (`COLS`). `generate.py` reads the part before the `cols=`
marker.
- `package-inventory.json` — generated map of every installed package to the
@@ -62,16 +72,21 @@ Node; the DOM glue is covered by the browser hash gates.
- `build-inventory.el` — refreshes `package-inventory.json` from a running
Emacs.
- `theme-studio.html` — generated output. Regenerate; don't hand-edit.
+ Use `make check-generated` before review if you want to verify the committed
+ page matches the generator without leaving the tree dirty.
## What it captures
Three tiers of faces, plus the palette:
-- **Palette** — named colors, shown grouped into hue *families* (see Color
- families below). Add by hex or with the in-page color picker
+- **Palette** — named colors, shown grouped into stable structural columns. Add
+ by hex or with the in-page color picker
(saturation/value square, hue slider, palette reuse chips, live contrast
readout, and an any / AA+ / AAA legibility mask). Remove and rename per chip;
- the colors serving as background and foreground are locked.
+ the colors serving as background and foreground are locked. `clear palette`
+ removes every non-ground color and leaves only the `bg` and `fg` tiles; existing
+ face assignments remain on their old hexes and show as "(gone)" until a color
+ with the same name is recreated.
The picker also shows perceptual readouts beside the WCAG ratio: the OKLCH
coordinates (lightness, chroma, hue°) and the APCA Lc contrast against the
@@ -87,42 +102,67 @@ Three tiers of faces, plus the palette:
snaps to the reachable color and shows a clamp note. The palette also warns
when two colors fall below a perceptual ΔE threshold, hard to tell apart.
- **Syntax** — every font-lock / tree-sitter category (keyword, string,
- function, type, comment, and the rest), each with normal/bold/italic and a
- contrast rating. Click a category to flash its tokens in the code; click a
- token to flash its row.
+ function, type, comment, and the rest), each with foreground, background,
+ style, box, and a contrast rating. Click a category to flash its tokens in the
+ code; click a token to flash its row. `lock all` flips to `unlock all` when
+ every row in the tier is locked. `reset` restores editable rows to the captured
+ syntax defaults; `erase` blanks editable rows. Both preserve locked rows.
- **UI faces** — cursor, region, mode-line, fringe, line numbers, isearch, paren
match, link, error/warning/success, and the rest, foreground and background
- per face, shown in a live mock Emacs buffer.
+ per face, shown in a live mock Emacs buffer. `reset` restores captured UI face
+ defaults; `erase` blanks editable rows to no explicit fg/bg. Both preserve
+ locked rows. Box controls include style plus an optional color; raised/pressed
+ boxes derive their relief edges from that color when set.
- **Package faces** — per-package face tables with a live preview (below).
-## Color families
-
-The palette is displayed as **families**: colors grouped into vertical columns by
-their actual color, dark at the top and light at the bottom, columns arranged left
-to right. Grouping is derived from the hex on every render — never from the name —
-so renaming a color to anything never moves it between columns. The flat palette
-underneath is unchanged (export stays a flat `[hex, name]` list); families are a
-view over it, and the per-chip rename/remove still work.
-
-- **Grouping.** Chromatic colors bucket by their nearest perceptual hue (red,
- orange, yellow, green, teal, blue, purple, pink). Near-neutrals — grays, the
- background and foreground ramps — collapse into one neutral column ordered by
- lightness, using a lightness-scaled chroma threshold so a faint pale tint keeps
- its hue while a faint mid gray reads as neutral. Columns sort by hue; the ground
- strip (the `bg` and `fg` assignments) pins first, neutrals next. (Hue-adjacent
- warm colors like olive-greens and golds can still share a column — a known
- limitation, since by hue they really are adjacent.)
-- **The count control** under each chromatic column sets how many steps sit on
- each side of the family's base (its most-saturated color). Setting N regenerates
- the family as a symmetric base ±N tonal ramp via `ramp()` — lighter and darker
- steps on the base's hue with chroma easing toward the extremes — *replacing* the
- column's current colors. N=0 collapses to the base alone.
-- **Editing a base** recolors the whole family: change a base color and the family
+Color dropdowns in the face tables use compact square swatches to save horizontal
+space. Hovering a swatch shows the color name and hex; clicking it opens the full
+palette dropdown.
+
+## Color columns
+
+The palette is displayed as **columns**. The ground column is pinned first: `bg`
+at one end, `fg` at the other, with optional `ground+N` span colors between them.
+Every other color stays in the column where it was created. Columns are not
+derived from hue, chroma, lightness, or the visible color name.
+
+- **Grouping.** Each palette entry carries a stable column id. New colors start
+ their own column; generated ramp steps inherit the base color's column id.
+ Renaming a color only changes its label, so a renamed tile stays in its original
+ column. Older two-field palette entries still load by falling back to the
+ generated-name stem (`blue-1`, `blue`, `blue+1` -> `blue`).
+ Generic Emacs names like `color-22` stay separate base columns unless they
+ already carry an explicit column id. Numbered named colors such as `blue1`,
+ `grey80`, `orange3`, and `orchid4` group by their text stem. Imported names
+ that begin with `bg` or `fg` are normal colors unless they are exact ground
+ endpoints or explicitly use the `ground` column id.
+- **Deleting.** Normal columns have a separated header delete control with a
+ confirmation prompt. Confirming removes every tile in that column. The ground
+ column is pinned and cannot be deleted. Face assignments that used a deleted
+ tile stay on that old hex and appear as recoverable "(gone)" values, matching
+ individual chip deletion.
+- **Tile clicks.** Single-clicking a tile, including its name, selects that
+ whole color. Double-clicking the name enters name-edit mode with the cursor at
+ the start of the name.
+- **The count control** under each non-ground column sets how many steps sit on
+ each side of the column's base. Setting N regenerates the column as a symmetric
+ base ±N span: N interior OKLab steps from black to the base and N interior
+ OKLab steps from the base to white. Pure black/white endpoint duplicates and
+ rounded base duplicates are skipped. The current UI caps N at 8; N=0 collapses
+ to the base alone.
+- **Editing a base** recolors the whole column: change a base color and the column
regenerates from it at the same count.
- **References follow.** When a regenerate changes a step's hex, any face assigned
to that step is re-pointed to the new hex. A step *removed* by lowering the count
leaves its references showing "(gone)" — visible and recoverable, never a silent
jump to a different color.
+- **Dropdown order.** Color dropdowns show the default entry, then `bg` and `fg`,
+ then palette columns from left to right. Within each column's dropdown group,
+ colors are ordered lightest to darkest.
+- **Dropdown arrows.** Color dropdowns in the syntax, UI, and package face tables
+ have left/right arrows. Left steps to the next darker color in the selected
+ color's column; right steps to the next lighter color. The arrows are disabled
+ for defaults, gone colors, locked rows, and column ends.
The standalone ramp generator is gone; fanning a color into a ramp is now "add the
color, then raise its column's count."
@@ -156,8 +196,20 @@ not a tool bug.
Pick an application from the dropdown to edit its faces. Each row has a
foreground and background dropdown, bold/italic toggles, an `inherit` dropdown
(base faces like `fixed-pitch`/`link` plus the app's own faces), a relative
-height stepper, a contrast readout, and a per-face reset. There's a per-app
-reset and a text filter for the large sets.
+height stepper, a contrast readout, box style/color controls, and a per-face reset. There's a per-app
+reset and a text filter for the large sets. Package `reset` restores editable
+rows to the captured package defaults; `erase` blanks editable rows to no
+fg/bg/style/inherit override. Both preserve locked rows. Package
+`lock all` / `unlock all` applies to the whole currently selected package, not
+only the rows visible under the text filter.
+
+Org TODO keyword colors are normal Org face resolution, not a separate automatic
+palette generator. Org checks `org-todo-keyword-faces` for an exact keyword match
+first. If no exact face is configured, keywords before the `|` separator in
+`org-todo-keywords` use `org-todo`; keywords after `|` use `org-done`. For
+example, in `(sequence "TODO" "WAIT" "|" "DONE" "CANCELLED")`, `TODO` and
+`WAIT` fall back to `org-todo`, while `DONE` and `CANCELLED` fall back to
+`org-done`. Fast-selection keys such as `WAIT(w)` do not affect the face.
Twenty applications have bespoke previews that exercise nearly all of their
faces: org-mode (a document plus an agenda view), magit (a status buffer plus
@@ -205,10 +257,15 @@ The export (and what a build step consumes):
```json
{
"name": "dupre",
- "palette": [["#67809c", "blue"], ["#e8bd30", "gold"]],
- "assignments": {"kw": "#67809c", "str": "#5d9b86", "bg": "#000000", "p": "#ffffff"},
- "bold": ["kw", "fnd"],
- "italic": [],
+ "palette": [["#67809c", "blue", "blue"], ["#e8bd30", "gold", "gold"]],
+ "syntax": {
+ "bg": {"fg": "#000000", "bg": null, "bold": false, "italic": false,
+ "underline": false, "strike": false, "box": null},
+ "p": {"fg": "#ffffff", "bg": null, "bold": false, "italic": false,
+ "underline": false, "strike": false, "box": null},
+ "kw": {"fg": "#67809c", "bg": null, "bold": true, "italic": false,
+ "underline": false, "strike": false, "box": null}
+ },
"ui": {"region": {"fg": null, "bg": "#264364"}, "cursor": {"fg": null, "bg": "#a9b2bb"}},
"packages": {
"org-mode": {
@@ -220,28 +277,32 @@ The export (and what a build step consumes):
}
```
-- `assignments` maps syntax category keys to hexes; `bg` is the `default` face
- background, `p` the foreground.
+- `syntax` maps syntax category keys to full face specs. `syntax.bg.fg` is the
+ `default` face background, and `syntax.p.fg` is the `default` face foreground.
+ Other syntax categories fan out to the font-lock / tree-sitter faces listed in
+ `build-theme.el`.
+- `palette` is a flat list of `[hex, name, columnId]`. `name` is the editable
+ display label; `columnId` is the durable grouping key that keeps generated
+ colors in their original column even if they are renamed.
- `ui` and `packages` faces carry `fg`/`bg` (hex or `null`), `bold`, `italic`,
- `underline`, `strike`, and for package faces `inherit` (a face name or
- `null`), `height` (a float, omitted at 1.0), and `source` (`"default"` seeded,
- `"user"` edited, `"cleared"`). The converter writes `underline` as
- `:underline t` and `strike` as `:strike-through t`.
+ `underline`, `strike`, and `box`; package faces also carry `inherit` (a face
+ name or `null`), `height` (a float, omitted at 1.0), and `source`
+ (`"default"` seeded, `"user"` edited, `"cleared"`). The converter writes
+ `underline` as `:underline t`, `strike` as `:strike-through t`, and `box` as an
+ Emacs `:box` plist.
- The theme name is both the `name` field and the download filename. Import a
`theme.json` to start from a prior theme; a file with no `packages` key still
loads.
-`export` always downloads a fresh file; `save` (shown once a name is entered)
-writes the same file in place via the File System Access API.
+`export` downloads the current theme JSON using the theme name as the filename.
## Build step — `build-theme.el`
`build-theme.el` converts a `theme.json` into a single self-contained
`themes/<name>-theme.el` deftheme. JSON in, valid Emacs faces out, across all
-four tiers: `default` from `assignments.bg`/`.p`, the syntax categories mapped
-to their font-lock / tree-sitter faces (with the `bold`/`italic` sets applied),
-the UI faces passed through, and the package faces with `:inherit`/`:height`
-and weight/slant written.
+four tiers: `default` from `syntax.bg`/`syntax.p`, the syntax categories mapped
+to their font-lock / tree-sitter faces, the UI faces passed through, and the
+package faces with `:inherit`/`:height` and weight/slant written.
```bash
emacs --batch -l scripts/theme-studio/build-theme.el \