aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/run-tests.sh
Commit message (Collapse)AuthorAgeFilesLines
* fix(theme-studio): derive box bevel colors from the face backgroundCraig Jennings17 hours1-1/+1
| | | | The released/pressed bevel was a flat translucent white/black overlay, which reads weaker than the box Emacs draws. reliefColors in colormath.js now ports Emacs 30's x_alloc_lighter_color: highlight = bg x1.2, shadow = bg x0.6, an additive boost for dark backgrounds, and the same-color fallback for pure black and white. boxCss takes the face's effective bg and derives both edges from it. Pressed swaps the pair, and the translucent pair remains only when no bg is known. Width stays 1px because dupre's :line-width -1 draws 1px lines in Emacs too. The gap was color strength, not width. Five node tests pin hand-computed fixtures from the C source, and a new #beveltest gate pins the wiring.
* fix(theme-studio): guard Chrome profile dir and bound headless rendersCraig Jennings23 hours1-2/+5
| | | | A headless render with an empty --user-data-dir falls back to the real Chrome profile and takes its SingletonLock. A hung render held that lock for 18 hours on 2026-06-09, blocking every interactive Chrome launch. The runner now refuses to run the browser gates when mktemp fails, and each render runs under timeout --kill-after so a wedged Chrome dies in seconds.
* feat(theme-studio): color-families export round-trip and README close-outCraig Jennings31 hours1-1/+1
| | | | | | | | Export stays a flat palette and import needs no reconstruction, because families are derived from the hex every render rather than stored. A #roundtriptest gate confirms export to import to export is byte-identical, and that the exported palette is still a flat [hex, name] list. Package seeding is unaffected since it reads the same flat palette. The spec's planned ramp-step warning exemption is dropped after analysis: a generated ramp's steps are a stepL apart, well above the too-similar ΔE threshold, so they never trigger the warning, and exempting same-family pairs would hide genuine near-duplicates that should be flagged (the case #deltatest checks). So the warning stays on the full palette. README documents color families: the hue grouping and its limitation, the ground strip, the per-column count control and regenerate, removed-step references reading "(gone)", and the removal of the standalone ramp panel. Phase 6, the last phase; the color-families v1 build is code-complete.
* feat(theme-studio): base-edit recolors a family; retire the ramp panelCraig Jennings31 hours1-1/+1
| | | | | | | | Editing a family's base now recolors the whole family: update-selected on a base with a ramp regenerates the family from the new base at the same count, so references follow the new hexes (shared regenFamilyInPlace with the count control). Editing a ground swatch already writes the bg/fg assignment through the existing repoint, and the gate confirms it. The standalone ramp panel is gone — its button, panel, JS, CSS, and the #ramptest gate are removed. Fanning a color into a ramp now happens from its strip: add a color, then raise its column's count. The ramp() math stays in app-core; only the duplicate UI is retired. Phase 5 of the color-families spec. A #baseedittest gate covers the base-edit recolor (family follows, references repoint, count preserved) and the bg-swatch edit writing the assignment.
* feat(theme-studio): add the live per-family count controlCraig Jennings31 hours1-1/+1
| | | | | | | | Each chromatic family column gets a count input (0-4) showing its current per-side reach. Setting N regenerates the family as a symmetric base ±N ramp from its most-saturated color, replacing the family's current members. A reference to a surviving step (matched by signed lightness rank) follows the new hex through repointHex; a reference to a step removed by lowering N is left on its old hex, which is no longer in the palette and renders as "(gone)" — never silently reassigned. The neutral and ground strips get no control. I also fixed the neutral threshold curve: it was flat-high through the darks, which pulled a chroma-eased dark ramp step (a dark desaturated blue) into the neutral column and broke the family. The curve now tapers toward both lightness extremes, peaking near mid, so dark and light tints both keep their hue while mid grays stay neutral. This is the symmetric form of the Munsell scaling and a strict improvement. Phase 4 of the color-families spec. A #counttest gate covers count-up adding symmetric steps, count-down dropping the extremes, the surviving-step repoint, and the removed-step "(gone)".
* feat(theme-studio): render the palette as hue family stripsCraig Jennings32 hours1-1/+1
| | | | | | The palette panel is now a stack of strips: the pinned ground strip (bg, fg) first, then hue-sorted family strips, each dark to light. Grouping comes from familiesFromPalette off the hex every render, so renaming a color never moves it. The flat PALETTE stays the editable truth and chips keep their per-chip remove / rename / select; the move-arrow and drag reordering are gone since the sort is deterministic now (moveColor and the drag state with them). Phase 3 of the color-families spec. A #familytest gate checks the ground strip pins first, families render, chips keep their controls, and a color renamed to anything stays in the same strip. Existing palette flows (delta, heal, ramp gates) stay green.
* feat(theme-studio): re-bind "(gone)" assignments when a name returnsCraig Jennings37 hours1-1/+1
| | | | | | Deleting a palette color leaves any assignment pointing at it showing "(gone)". Recreating a color with the same deleted name now re-points those stranded assignments to the new color, even when its hex differs, instead of leaving them stuck on the old hex forever. Delete records the removed name and hex; the next add of that name re-points every reference (syntax map, UI faces, package faces) to the new hex and consumes the record. The registry clears on import so a stale name from a previous theme can't re-bind across a load. I pulled the re-point loop that update-selected already used into a shared helper. A #healtest gate covers delete-then-recreate-with-a-new-hex.
* feat(theme-studio): mark safe lightness in the OKLCH pickerCraig Jennings38 hours1-1/+1
| | | | | | | | The OKLCH picker gets a "safe for" selector listing the covered overlay faces. Pick one and the C×L plane shades the lightness band too light to keep that face readable over its foreground set, with the L_max ceiling as the band's lower edge. The ceiling is one marker computed via lMax at the current chroma, not a per-pixel foreground-set mask over the plane, so the existing AA/AAA mask stays single-foreground. When no foreground is dark enough to fail, the band hides; when even black can't satisfy the target, the whole plane shades. The band only shows in OKLCH mode and clears in HSV. The cursor moved above the band so it stays visible through the shade. Phase 5 of the palette-ramps spec, the last build phase. A #safetest browser gate pins that the band appears for a selected covered face with a positive height and hides when none is selected.
* feat(theme-studio): show worst-case contrast for overlay facesCraig Jennings38 hours1-1/+1
| | | | | | | | The five covered overlay faces (region, hl-line, highlight, lazy-highlight, isearch) now show the worst-case floor over their foreground set instead of one fg/bg pair. The cell starts with "worst:", names the limiting foreground by role and hex, then gives the ratio and a PASS/FAIL verdict, so a tint that clears the default text but fails the darkest token can't hide. The foreground set is the live syntax-token colors plus the default fg; the verdict is WCAG AA by default. Package and other UI rows keep their single-pair readout. A syntax-color edit now repaints the covered faces, since their floors depend on the whole token palette, not just their own fg/bg. An out-of-scope face falls through to the single-pair cell, and a face whose set resolves empty reads "no fg set" rather than a bogus ratio. Phase 4 of the palette-ramps spec. A #contrasttest browser gate pins the readout, the keyword-blue limiting case, the single-pair fallback, and the no-set message.
* feat(theme-studio): add the ramp UI in the paletteCraig Jennings38 hours1-1/+1
| | | | | | | | A ramp button on the palette controls opens a panel that generates a tonal ramp from the current color and previews the steps. Each step is a swatch labeled with its derived name (blue, blue+1, blue-1) and a clamp badge when the color left the sRGB gamut, so an out-of-gamut step is visible before it's added. The n, stepL, and chroma-ease controls default to 2 / 0.08 / 0.5 and re-preview live. Clicking a step adds it to the palette; "add all" adds the lot. Steps insert adjacent to the source swatch in -n..+n order. A name collision is flagged and skipped rather than overwriting an existing color, and a generated hex that already matches another entry is added but flagged as a duplicate. This is Phase 2, the DOM around the pure ramp() from Phase 1. A new #ramptest browser gate pins the step count, the ordered insertion after the source, the collision skip, and the clamp badge on an out-of-gamut step.
* fix(theme-studio): make the live buffer preview render UI faces the way ↵Craig Jennings47 hours1-1/+1
| | | | | | | | | | | | | | | | Emacs does The mock buffer rendered several faces wrong, verified against the running daemon and emacs -Q: - highlight was flat monochrome text. Like region, the highlight face is background-only, so Emacs lets the syntax foreground show through (face merge: highlight over a keyword keeps the keyword color, adds the highlight background). Both now go through one overlay helper that keeps token colors and only overrides the foreground when the face sets one. - cursor was an empty block at end-of-line. Emacs draws a box cursor on the character at point, in the frame background over the cursor color — now it sits on a real glyph. - the overlay faces, the line-number gutter, and the paren faces ignored weight/underline/etc. They now honor bold/italic/underline/strike, matching the table preview. - the fringe showed only its background. It now draws a continuation indicator in the fringe foreground. - the mode line had no box. It now carries the 3D released-button box that is the Emacs default. Defaults seeded to the theme-independent Emacs core styles (from emacs -Q): error/warning/success are bold; lazy-highlight and show-paren-match are underlined (link already was). Added a #mocktest gate pinning these — overlay faces keep token colors, the cursor is on a glyph, styles render, the fringe indicator is present, the mode line has its box. Verified it goes red when the rendering regresses.
* build(theme-studio): add a local Makefile; root delegates test + coverageCraig Jennings2 days1-1/+7
| | | | | | | | theme-studio is a self-contained Python + JS subproject with its own toolchain (python3, node, uvx, headless Chrome), unrelated to the root Makefile's Elisp/ERT world. Gave it a local Makefile that owns that toolchain — test, check (fast, no browser), coverage, gen, open — so the build logic lives with the code and the short target names don't collide with the root's Elisp-flavored test/coverage. The root keeps the discoverable entry points: theme-studio-test and a new theme-studio-coverage now delegate via make -C. run-tests.sh grows a --no-browser flag so `make check` can skip the headless-Chrome gates for a fast inner loop. gen/open take an optional SEED to view a specific palette. coverage reports both halves: node --experimental-test-coverage for the three JS modules (all 100% line, ~96% branch) and uvx coverage for generate.py (89% lines; the rest is the __main__ writer and the optional seed-env branch).
* refactor(theme-studio): unify the syntax table onto the shared sortCraig Jennings2 days1-1/+1
| | | | | | | | The syntax table had its own sort (srt + a D{} direction map) that read state directly — MAP[kind] for the color column, cell text for elements. The UI and package tables used a separate, more general system (srtTable / cellVal / applyTableSort) that reads the rendered cells. Pointed the syntax headers at srtTable('legbody', col) and deleted srt, so all three tables share one sort. The mapping is exact: the legtable color cell is a swatch dropdown whose data-val is the hex, which cellVal reads — same key srt sorted on — and the elements cell is text. First-click direction stays ascending. The syntax table sorts on click only; it doesn't opt into the cross-rebuild persistence the UI and package tables get from applyTableSort, which preserves its prior behavior. Added a #sorttest gate: sort was previously untested, and this collapses two systems into one. It checks the syntax table sorts by color ascending, reverses on a second click, sorts by element name, and that the UI and package tables still sort. The asc/desc pair is self-validating — a no-op sort can't pass both.
* test(theme-studio): pin lock behaviors with a #locktest gateCraig Jennings2 days1-1/+1
| | | | Adds a browser hash gate covering the two lock behaviors no existing gate touched: locking a row disables its control (syntax swatch div via data-locked, UI select via .disabled, both through the shared mkLockCell), and clear-unlocked wipes unlocked rows to default while leaving locked rows untouched across all three tiers. This is the characterization net for the generate.py extraction refactor — it proves the upcoming CSS/JS move preserves lock behavior. Verified it goes red when a lock guard is removed.
* test(theme-studio): add a one-command test runner and make targetCraig Jennings2 days1-0/+73
The browser hash gates were run by hand through headless Chrome, so a picker-JS regression only surfaced if someone remembered to run them. run-tests.sh now drives the whole pyramid in one command: regenerate the page, the Python templating tests, the Node unit tests plus inline-integrity, a syntax check of the spliced page script, and the six browser hash gates. It exits non-zero on any failure, and make theme-studio-test calls it. The browser gates need a Chromium-family browser. When none is found they report SKIPPED rather than passing, so a machine without Chrome can't turn the gates silently green.