aboutsummaryrefslogtreecommitdiff
path: root/scripts/theme-studio/test_generate.py
Commit message (Collapse)AuthorAgeFilesLines
* feat(theme-studio): pin the remaining chrome at absolute heightsCraig Jennings35 hours1-16/+22
| | | | | | tab-bar, tab-line, line-number, and line-number-current-line join mode-line in the chrome height seeds (apply_modeline_height_default generalized to apply_chrome_height_defaults), each pinned at absolute 130 so no bar or gutter tracks a buffer's enlarged default face. header-line and mode-line-inactive stay unseeded on purpose: both inherit mode-line, so the pin reaches them through the chain and their own value would duplicate state. The line-number pair is seeded individually because the generated theme's explicit specs leave their :inherit unspecified at runtime. header-line, tab-bar, and tab-line also join the UI faces table, so all chrome heights are editable through the size column; the mock-completeness gate exempts the three faces the mock deliberately doesn't draw. WIP.json reconciled and the theme regenerated; every chrome face resolves :height 130 in the live daemon, pins and inherit chains both.
* feat(theme-studio): explicit absolute-vs-relative face height kindCraig Jennings36 hours1-0/+26
| | | | | | JSON collapses 2.0 to 2 on save, so a height's number type can't say whether it's a fixed 1/10pt value or a relative multiplier. The face model now carries an explicit heightMode field (abs/rel) through seed, save/load, and export. build-theme.el coerces :height from the kind: abs exports an integer, rel a float, so a relative 2.0 renders as 2.0, never 2. Faces saved before the field existed infer the kind once on load (JS: integer to abs, fractional to rel; Python keeps the authored type, so a float 2.0 seed stays relative) and persist it on the next save. The mode-line seed carries abs explicitly, and WIP.json's eight seeded heights are stamped with their kinds. Regenerating the theme from the stamped WIP.json produces an identical WIP-theme.el, so the round-trip holds.
* fix(theme-studio): pin mode-line at an absolute heightCraig Jennings37 hours1-0/+19
| | | | | | | | | | | mode-line's :height was unspecified, so a buffer that remaps its default face larger (the nov reading view) inflated its modeline with it. Seed an absolute 130 (1/10pt) on mode-line — build_uimap gains apply_modeline_height_default, mirroring the hover-box default — and set it in WIP.json. Also drop the stray :height 2 from mode-line-inactive (a JSON integral-float collapse that rendered inactive bars at 0.2pt); inactive now inherits mode-line's height. Theme regenerated and loaded live; the editable-height spec covers making this tunable in the studio.
* feat(theme-studio): wire the ansi-color scene, closing its dangling preview keyCraig Jennings45 hours1-1/+1
| | | | ansi-color carried a bespoke spec and a full seed palette but its renderer was never written, so it silently fell back to the generic face list. Added renderAnsiColorPreview — a compilation/shell buffer exercising all 16 SGR palette faces, the faces eat and vterm color faces inherit from — and registered it. Last generic app in the ecosystem-coverage scope.
* feat(theme-studio): scenes for pinned ghostel and all-the-iconsCraig Jennings45 hours1-0/+1
| | | | ghostel (the terminal behind the agent buffers) gets an eat-style scene through its own 16 ANSI color faces plus the default surface and the two fake-cursor faces; all-the-icons a dired listing where each file's leading marker carries its color face across the 8 hues, their light/dark variants, and the -alt accents. Both are pinned not-loaded apps whose only visibility is the studio preview, so they carry the highest realism bar. Face names are literal, not constructed, to satisfy the coverage gate.
* feat(theme-studio): scenes for tmr, wttrin, alert, org-superstar, ↵Craig Jennings45 hours1-0/+1
| | | | | | nerd-icons-completion tmr shows the list view and tmr-tabulated-view with every column face; wttrin the weather buffer with staleness header, key hints, and the stale modeline lighter; alert one line per severity; org-superstar the bullet swap across levels plus list items; nerd-icons-completion folder icons in the dir face. Phase 4 of the preview run — all 13 small-scene apps now sit in the face-coverage gate.
* feat(theme-studio): scenes for indent-guides, yasnippet, prescient, ↵Craig Jennings45 hours1-0/+1
| | | | | | flyspell-correct highlight-indent-guides shows both methods (character glyphs, column bands) plus the responsive top/stack variants at a marked point; yasnippet a mid-expansion snippet with the active field and the debug overlay; prescient primary/secondary match highlights; flyspell-correct the word under correction with its menu. All four join the face-coverage gate.
* feat(theme-studio): scenes for emms, org-roam, hl-todo, symbol-overlayCraig Jennings45 hours1-0/+1
| | | | emms shows its three UIs (browser tree, playlist, metaplaylist); org-roam the backlinks buffer plus the dailies calendar mark; hl-todo comment keywords and the flymake diagnostic; symbol-overlay all eight pin faces on one defun plus the at-point default. All four join the face-coverage gate.
* feat(theme-studio): minibuffer-stack scenes on one shared sessionCraig Jennings45 hours1-0/+1
| | | | One C-x b consult-buffer scene drawn jointly by vertico, marginalia, consult, embark, and orderless; each app's preview shows the shared session with its own faces at work, then extends it with the states only it owns (vertico multiline, marginalia's full annotation catalog, consult line/grep/async, embark act + collect, orderless component cycling). All five join the face-coverage gate, so every one of their 72 faces appears verbatim in the renderers.
* feat(theme-studio): web-mode scene covering all 81 faces + HTML sampleCraig Jennings46 hours1-1/+1
| | | | The web-mode preview is one mixed document: markup with every tag/attr variant, an inline CSS part, a generic template block (engine-agnostic on purpose), and a script part carrying a JSON island, JSX depths, nested template literals, SQL-in-a-string, a PHP preprocessor island, and JSDoc annotations. The realism gate now covers it, so all 81 faces are exercised. SAMPLES gains an HTML language, which also enriches the syntax and auto-dim previews.
* feat(theme-studio): realistic scenes for company, transient, and friendsCraig Jennings46 hours1-0/+28
| | | | Five daily-driver apps leave the generic face-name list: company (a completion popup with every row variant, inline ghost preview, tooltip search, and the echo frontend), company-box (the icons variant), transient (a magit-commit-style panel exercising every key class), magit-section (status-buffer headings with child counts and the highlighted section), and rainbow-delimiters (buffer-honest nesting depths 1-9 plus the cycle past 9 and both error faces). A new test gates realism: every face of each covered app must appear verbatim in its renderer, so a scene can't silently skip faces.
* feat(theme-studio): pin retired packages so themes keep covering themCraig Jennings46 hours1-0/+50
| | | | PINNED_PACKAGE_FACES is the curated record of packages retired from the config (ghostel, all-the-icons). A pinned package survives inventory regeneration, shows a 'not loaded' label + hover in the app dropdown, and keeps refreshing its face list from the live inventory while that still carries it. An attempted live regen made the fragility concrete: today's daemon session was missing 142 faces from lazily-loaded packages, so regen-from-live is inherently lossy and the pin is the only durable record.
* refactor(term): finish ghostel retirement (phase 5)Craig Jennings8 days1-1/+0
| | | | Remove the dead ghostel app from theme-studio: the GHOSTEL_FACES/SEED data, the registry row, the renderGhostelPreview previewer, and the package_seed test, then regenerate the tool. ansi-color stays since eat inherits it. Rename testutil-ghostel-buffers to testutil-terminal-buffers and drop make-fake-ghostel-buffer; the toggle-filter test now uses the eat fixture, since agents are eat. Fix the comments that still called the agent buffers ghostel (they're eat now) in eat-config and the ai-term and auto-dim test docstrings. I also package-deleted the unused ghostel ELPA package. Full suite green; the remaining ghostel mentions are accurate migration history.
* refactor(theme-studio): tier-1 simplification passCraig Jennings10 days1-0/+33
| | | | | | | | These are behavior-preserving cleanups from the refactor/simplify assessment, all test-verified. I merged syncMockHeight and syncPkgHeight into one syncPaneHeight(tableId, paneId), inlined the two single-use displayHex/displayName closures, dropped a pkgbody guard that buildPkgTable already does, and had paintUI call worstCellHtml instead of rebuilding the covered-contrast cell. I deleted the dead generatorHues "manual" branch (a copy of the fallback) and locateInfoLine (orphaned when I removed the preview info line earlier today). The two nerd-icons loaders now share _load_nerd_icons_artifact, with a sentinel so a null-file edge keeps its exact behavior. face_coverage.classify reads through named locals now, guarded by a new characterization test. Two assessment findings were wrong and skipped after I checked them against the code: LOCATE_REG is live (read by previewSpan), and normalizePaletteEntryCore doesn't exist.
* feat(theme-studio): nerd-icons gallery as a hue-ordered icon gridCraig Jennings10 days1-0/+109
| | | | | | | | | | The nerd-icons pane is now a grid: one row per color face, the rows ordered by hue so families cluster, distinct icons (deduped within a color) drawn in their color with the icon's nerd-font name beneath. A "preview:" dropdown above the grid picks the glyph size in points, with Left/Right arrows to step it. Single-pane apps show it disabled, naming the preview. This replaces the v1 legend in the pane, whose data is still captured for round-trip. build-nerd-icons-legend.el is now a library. A cj/nerd-icons-write-legend entry point requires nerd-icons only at write time, so the capture logic loads and unit-tests without it. It dedupes icons by name within a face, computes each face's native hue, and orders the groups by hue. Writing the test surfaced a latent bug: face-hsl used (cadr (assoc t spec)), which grabs the first keyword instead of the plist. It only worked because the real faces fall through to the face-foreground branch. I fixed it to a correct t-clause parse. Coverage: 7 ERT capture tests (dedupe, hue order, lightness tiebreak, name sort, skip rules), 4 Python validator edges, and browser gates for the grid and the size dropdown. Locate stays color-level: clicking a color flashes its icons, and clicking an icon flashes its color row. Icons aren't individually editable, so there's nothing per-icon to select.
* feat(theme-studio): bespoke nerd-icons filetype-legend preview (phase 2)Craig Jennings10 days1-0/+15
| | | | | | Register nerd-icons as a bespoke app whenever its captured legend is valid: the 34 color faces stay editable rows, and the legend rides APPS['nerd-icons'].legend. A new renderNerdIconsPreview draws each curated filetype's glyph in its mapped face's effective color, read through the same registry the other previews use, so recoloring a face repaints every row mapped to it. When the legend is absent the generic inventory app stands in. The #nerdiconstest browser gate covers the wiring, the dir-row owner, and the recolor-repaint. Claude-Session: https://claude.ai/code/session_01BqrdWUo9GcznYX2pZr76gZ
* feat(theme-studio): capture the nerd-icons filetype legend (phase 1)Craig Jennings10 days1-0/+45
| | | | | | Add build-nerd-icons-legend.el, which resolves the curated v1 legend rows (glyph + owner color face per filetype) from the live nerd-icons alists and dumps them to nerd-icons-legend.json, a committed artifact like package-inventory.json. generate.py gains load_nerd_icons_legend, which validates the artifact and returns None — with a warning — when it is absent, malformed, empty, or missing a field, so the page can fall back to the generic nerd-icons app rather than error. Data only; the bespoke preview that renders it lands next. Claude-Session: https://claude.ai/code/session_01BqrdWUo9GcznYX2pZr76gZ
* refactor(theme-studio): dedup the inline-integrity test scaffoldingCraig Jennings14 days1-25/+11
| | | | | | | | | Two test-DRY cleanups. The seven near-identical test_page_carries_*_verbatim methods in test_generate.py collapse into one subTest loop over the inlined-body names. The strip-exports helper -- reimplemented three times across the colormath, app-core, and app-util inline-integrity tests, each annotated 'same strip generate.py applies' -- moves to one shared inline-strip.mjs (stripInlinedBody), so the three copies can no longer drift from generate.py's strip_exports.
* feat(theme-studio): add 18 language previewsCraig Jennings2026-06-201-0/+14
| | | | Add tokenized code samples for Racket, Scheme, Haskell, OCaml, Scala, Kotlin, Swift, Lua, Ruby, Perl, R, Erlang, SQL, PHP, Ada, Fortran, MATLAB, and Assembly, wired into the language dropdown. Each is an idiomatic snippet tagged by syntax category so the studio renders it in the assignment colors. A guard test checks every added language is registered and renders a non-trivial sample.
* feat(theme-studio): show face docstrings in element hoversCraig Jennings2026-06-201-1/+14
| | | | | | Each table row's category cell now shows the face's Emacs docstring on hover, on top of whatever the cell showed before. The package cell keeps the face name underneath. The syntax and UI cells had no prior tooltip, so they show just the docstring. The label-span hints are left alone. I added face-docs-dump.el, which emits face-docs.json from a live Emacs: a face-name to first-doc-line map for the UI and package tables, and a category to doc map for the syntax table. The category to font-lock-face mapping is read from build-theme.el's own map, so it isn't copied a third time. generate.py inlines both maps. A pure composeHoverTitle helper composes the tooltip, covered by Node, Python, and a new browser gate.
* feat(theme-studio): seed faces with their default distant-fg, overline, ↵Craig Jennings2026-06-191-0/+23
| | | | | | | | | | inverse, and extend The studio now pre-fills the additive attributes a face carries in stock Emacs, so it opens closer to reality. default_faces.seed reads distant-foreground, overline, inverse-video, and extend from the captured snapshot, the same way it already reads weight, slant, underline, and strike. lazy-highlight, for example, opens with its black distant-foreground instead of blank, and the faces that extend to the window edge open with that flag set. The capture script gains overline in its attribute map and probe, so the next snapshot refresh records it too. I didn't re-run the capture here. distant-fg, inverse-video, and extend are already in the committed snapshot, so reading them seeds every face that has them, and stock faces almost never set overline. A fresh capture (python3 scripts/theme-studio/capture-default-faces.py) is a larger, separate refresh, since the new probe field touches all 687 face blocks. Imported presets are unaffected, since they carry their own face data and never re-read the snapshot. Only a fresh studio session sees the richer seeds. Full suite green: Python 61, Node 201, ERT 41, plus the browser hash gates.
* refactor(theme-studio): cut the face model over to weight/slant/objectsCraig Jennings2026-06-181-24/+34
| | | | | | | | | | I replaced the legacy bold/italic/underline/strike booleans with the final model shape across both sides of the tool. weight (light/normal/medium/semibold/bold/heavy) and slant (normal/italic/oblique) replace the bold/italic flags, underline becomes {style: line|wave, color}, strike becomes {color}, and null means unset. A single migration converts a legacy face on the way in, mirrored as migrateLegacyFace in app-core.js and migrate_legacy in face_specs.py so the JS and Python models can't drift. It runs on import (applyImported, mergePackagesInto) and on every seed that face_spec touches. The captured-snapshot seed (default_faces.seed) narrows the same way it did before. Only bold and italic survive, as weight "bold" and slant "italic", so the generated themes stay byte-identical. The B/I/U/S toggle buttons keep working through a transitional bridge (legacyStyleOn / toggleLegacyStyle). The weight/slant dropdowns and underline/strike controls that replace them land next. The live previews read the new shape, with a weight name mapped to a numeric CSS font-weight. The cutover is proven emit-neutral two ways. An ERT test asserts the migrated shapes emit the same attributes as the legacy booleans, and deep-migrating every face in dupre, distinguished, sterling, now, theme, and WIP then running build-theme yields byte-identical output. Full suite green: Python 59, Node 200, ERT 41, plus the browser hash gates.
* feat(theme-studio): widen the face model with the additive attributesCraig Jennings2026-06-181-1/+19
| | | | | | | | | | This is Phase 2 of the face-attribute expansion. The model now carries distant-fg, family, overline, inverse, and extend in final shape across all three tiers, and inherit and height are no longer package-only (a ui or syntax face can set them too). I kept bold/italic/underline/strike as the legacy booleans for now. The cutover to weight/slant and the underline/strike object forms lands in the next phase with the editor widgets that force it, so the representation and the controls that drive it move together. face_specs.py holds the canonical defaults. In app-core.js, normalizePkgFace and packagesForExport carry and emit the new attrs: distant-fg resolves through the palette like fg/bg, and each attr exports only when set, so existing presets re-export unchanged. app.js syntaxBlank, uiFaceBlank, and seedFace match the shape. Nothing changed shape, so dupre, distinguished, sterling, now, theme, and WIP all emit byte-identical themes. make check green: Python 58, Node 193, ERT 40.
* feat(theme-studio): add mode-line-highlight as an editable faceCraig Jennings2026-06-181-0/+13
| | | | | | | | The mode-line hover box (the raised bevel on clickable mode-line segments) came from mode-line-highlight, a face the studio never managed, so it fell through to Emacs's stock released-button default with no way to change it. I added it to the generated UI face list, between mode-line and mode-line-inactive. The row and box control are already generic over that list, so they appear automatically. build-theme.el's UI emission is generic too, so the elisp side needs nothing. The face isn't in the captured Emacs snapshot, so apply_hover_box_default seeds its box to the raised default in both build_uimap branches. That matches current behavior and leaves the user free to flatten or recolor it. The mock frame previews the hover by wrapping a mode-line segment in the face.
* feat(theme-studio): palette generator and preview fidelityCraig Jennings2026-06-141-0/+8
| | | | | | | | | | Two strands land together because the generated theme-studio.html bundles every source file into one page and can't be split cleanly. The palette generator is a preview-first panel: palette-generator-core.js plans the palette and palette-generator-ui.js draws it. Generated colors stay inspectable and tunable through the existing selector, and committing one creates a normal base column. It adds source-mode and scheme controls, a configurable accent count, and color names from color-names.json. For preview fidelity, syntax and UI colors now resolve through the real Emacs inherit chains, so the preview matches how Emacs renders the theme. resolveSyntaxFg pins dec to ty (Emacs has no decorator face) and otherwise follows comment-delimiter to comment, doc to string, property to variable, function-call to function-name. resolveUiAttr walks mode-line-inactive to mode-line and line-number-current-line to line-number. The decorator label now reads "decorator to type" to match the type face Emacs uses for it. Design recorded in the two theme-studio specs under docs/.
* Update theme studio palette workflowCraig Jennings2026-06-141-7/+144
|
* Add theme studio Rust and Zig samplesCraig Jennings2026-06-131-0/+29
|
* Refactor theme studio palette testsCraig Jennings2026-06-131-0/+7
|
* Group numeric color names by stemCraig Jennings2026-06-131-0/+3
|
* Treat legacy color names as base columnsCraig Jennings2026-06-131-0/+6
|
* Add theme studio default face drift summaryCraig Jennings2026-06-131-1/+40
|
* Pin theme studio generated defaultsCraig Jennings2026-06-131-0/+32
|
* Refactor theme studio face assemblyCraig Jennings2026-06-131-4/+31
|
* Guard theme studio package face coverageCraig Jennings2026-06-131-0/+35
|
* Extract theme studio default face adapterCraig Jennings2026-06-131-0/+60
|
* test(theme-studio): extract color/slug helpers to importable modules and ↵Craig Jennings2026-06-091-1/+41
| | | | | | | | | | cover them The pure helpers that were still stranded in app.js — normHex, ratingColor, textOn, and the filename-slug logic — had no unit tests because app.js can't be imported (it runs its bootstrap and references the data placeholders at load). Moved them into importable modules so they can be tested directly: a new app-util.js holds the color/UI-boundary trio, and slugify joins app-core.js. app.js keeps thin wrappers, so no call site changed and the built DOM is byte-identical. textOn needs rl from colormath, so generate.py's inline strip now drops import lines as well as export lines — app-util.js imports rl for its tests, and the import is stripped on inline where rl is already in the page. _faces in generate.py also gets direct tests for its prefix-strip and label derivation. New: 12 node tests (normHex, ratingColor, textOn, slugify) and 7 python tests (_faces, app-util integrity, the import strip). Coverage: app-util.js 100/100/100, app-core.js 100/94.9/100, colormath.js 100/96/100 (line/branch/func); generate.py 89% lines (the rest is the __main__ writer and the optional seed-env branch). No bugs surfaced — the logic was correct, just untested.
* test(theme-studio): extract app-core.js and unit-test the app logicCraig Jennings2026-06-091-1/+6
| | | | | | | | The refactor's goal was to make the app logic testable; this realizes it. Pulled the pure package-face model and the dropdown option list into app-core.js — nameToHex, buildPkgmap, packagesForExport, mergePackagesInto, effResolve (the inherit-chain resolver behind pkgEffFg/pkgEffBg), and optList — with every dependency passed as a parameter so there is no DOM and no module-global reliance. generate.py inlines it into the page the same way it inlines colormath.js (strip exports, placeholder, integrity check), so the browser runs the same code the tests import. app.js keeps thin wrappers (pname, seedPkgmap, ddList, pkgEffFg, pkgEffBg) that pass the live PALETTE / APPS / PKGMAP into the core, so no call site changed and the built DOM is byte-identical to before. test-app-core.mjs adds 18 Normal/Boundary/Error tests over the extracted logic — name resolution, the seed/export/merge round trip, the inherit chain including a cycle that must terminate at null, and the "(gone)" dropdown entry — plus an inline-integrity check that the page carries the core verbatim. The node suite goes 25 to 43 tests; python templating gains the app-core integrity assertion.
* refactor(theme-studio): extract CSS and JS to files, inline at generate timeCraig Jennings2026-06-091-0/+12
| | | | | | | | generate.py was 1378 lines, ~1300 of them a single triple-quoted string holding the whole app. Moved the <style> block to styles.css and the <script> body to app.js, and generate.py now inlines both through placeholders the same way it already inlines colormath.js, then fills the data placeholders. It drops to ~500 lines (the remaining bulk is the package face-data dicts, a later stage). The generated page is byte-identical to before — every hash gate, the node suite, the spliced-script parse, and the new #locktest stay green. Two integrity tests guard the splice: styles.css inlines verbatim, and app.js reaches the page exactly as fill_data renders it. Both go red if the splice wiring is dropped. Living in real files instead of a Python string kills the backslash-doubling bug class (str.replace is literal, so escapes survive), gives the CSS and JS real editor tooling, and opens the app logic to unit testing — the point of the whole refactor.
* test(theme-studio): make generate.py importable and test the templatingCraig Jennings2026-06-081-0/+84
The page generator's risky logic, the export-strip that inlines colormath.js and the placeholder substitution that fills in the sample and palette data, had no tests. A bug there ships a broken theme-studio.html the JS tests can't see, since they import colormath.js directly and never look at the assembled page. I pulled the strip into a strip_exports function and guarded the file write behind __main__, so importing generate.py builds the page in memory without writing it. test_generate.py asserts the strip removes export lines, preserves the body, and rstrips. It asserts the assembled page has every placeholder filled and carries the colormath body verbatim. And it guards that colormath.js keeps its export on a single line, since the line-based strip would leave a multi-line export's continuation behind. That was the exact failure that bit during the first integration.