| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
colspan, nd flag)
Three cleanups to the per-row expander from 3B-2.
The underline control moves from the in-row style cell into the expander, next to overline. The row keeps weight, slant, and strike inline, so the style cell drops from three wrapped rows to two and the table reads flatter.
mkExpander no longer hardcodes each table's colspan. tableColCount reads the column count from the table's header, so a detail row spans correctly even if a column is added later.
A collapsed expander now flags itself when it hides an attribute that differs from the face's default, so a non-default value is never invisible. overflowNonDefault (app-core.js, unit-tested) compares the expander's attributes against the default. The toggle re-checks after every edit and gets the gold marker when any differ. faceBoxNonDefaults drops underline from the in-row style box in the same move, since underline is now the expander's concern.
The #expandtest gate covers the underline control in its new home, its wavy write, and the flag appearing then clearing. Full suite green: Python 59, Node 201, ERT 41, plus the browser hash gates.
|
| |
|
|
|
|
|
|
|
|
|
|
| |
weight/slant/underline/strike controls
The B/I/U/S toggle buttons in the syntax, UI, and package tables become a weight selector (light/normal/medium/semibold/bold/heavy), a slant selector (normal/italic/oblique), and box-like underline and strike controls. The underline control sets line or wave plus a color, and the strike control sets a color. A face can now reach the full weight range and a wavy or colored underline, not just bold and italic on-off.
All four controls come from one mkStyleControls helper shared across the three tables, and underline and strike share mkLineStyleControl (the box-control pattern, parameterized for a styled line vs a plain toggle). With the real controls in place I dropped the transitional legacyStyleOn/toggleLegacyStyle shim and its tests.
The overflow attributes (distant-fg, family, overline, inverse, extend, and inherit/height for ui and syntax) move into a per-row expander next.
Verified by screenshot and the browser style gate, which now drives a weight-select change and an underline-wave click through the model. Full suite green: Python 59, Node 198, ERT 41, plus the browser hash gates.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
| |
I added left and right arrow buttons flanking the view dropdown. They step the selection to the previous or next item and re-render the faces table and preview, so you can walk the list without reopening the dropdown. A pure stepViewIndex helper clamps the index to the option range, no wrap. stepView sets the selection and calls onViewChange.
|
| |
|
|
| |
A non-default height looks identical to the default in the size input, so a stray 1.1 hides in plain sight. I added a small gold corner flag on any per-face setting cell (fg, bg, style, inherit, size, box) whose value differs from the face's seed default. A pure faceBoxNonDefaults helper computes the per-box flags. buildPkgTable resolves fg/bg to hex before comparing, so a palette-name-vs-hex difference doesn't read as a change.
|
| |
|
|
| |
The assignment-view dropdown listed package faces in APPS build order (bespoke apps first, then inventory). generate.py builds them that way, so the list wasn't alphabetical. I added a pure appViewKeysSorted helper that orders the app keys by display label, and buildViewSel uses it. The @code and @ui editor entries above the divider are unchanged.
|
| |
|
|
|
|
|
|
|
|
| |
- The color dropdown opens a grid, not a long list.
- The grid mirrors the palette: ground strip, then a row per family.
- Members run dark to light, with the current color outlined.
- A default chip clears the assignment.
- A (gone) cell shows a color no longer in the palette.
- The trigger and step buttons stay the same.
- All three tiers share the one dropdown.
|
| |
|
|
|
|
|
|
|
| |
- ramp (app-core.js) and its test-ramp.mjs: superseded by regenColumn, no production caller.
- optList (app-core.js) and its tests: superseded by paletteOptionList.
- ITALIC in generate.py: computed, never read (ITALIC_MAP is the live one).
- a stray empty string in MU4E_FACES that .split() silently dropped.
- the dead #familytest alias in the columntest gate, which HASHES never listed.
- widen face_rows to Sequence[str], clearing the list-invariance warnings on the APPS calls.
|
| |
|
|
|
|
| |
The color-picker popup colored each row's name and hex for contrast against the swatch, but the rows sit on the popup's fixed dark background. A mid or dark swatch (the blues past blue-1) got near-black text that vanished on the dark popup.
The text now inherits the popup foreground for every real palette color. Only the solid "default" row, whose background is the color itself, still contrasts against its own fill. I moved the decision into dropdownRowTextColor with unit coverage, including a dark-swatch regression case.
|
| |
|
|
|
|
|
|
|
|
| |
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/.
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| | |
|
| |
|
|
|
|
|
|
| |
ramp(baseHex, {n, stepL, chromaEase}) in app-core.js turns one base color into a tonal ramp: 2n steps at offsets -n..-1 and +1..+n, ordered darkest to lightest, base excluded. It holds the OKLCH hue, steps lightness by stepL, eases chroma toward the extremes so only the farthest step loses most of its color, and gamut-clamps each step with its own clamped flag.
Bad input returns a structured result rather than throwing: an unparseable base gives {steps: [], error: 'bad-hex'}, and out-of-range n/stepL/chromaEase clamp into range with the clamped knob named in adjusted. Defaults are n=2, stepL=0.08, chromaEase=0.5.
This is Phase 1 of the palette-ramps spec: pure logic, no UI. Tests cover mid/near-white/near-black bases, hue-hold, chroma easing, knob clamping, and malformed hex. The integrity stripper for app-core.js now drops import lines too, since the core imports normHex and the colormath helpers for the Node tests (stripped on inline, where both are already in scope).
|
| |
|
|
|
|
|
|
| |
The mode-line box in the preview was hardcoded — it showed a box the generated theme couldn't actually produce, since build-theme.el never emitted :box. Made :box a real face attribute instead: a per-face box object (style line/raised/pressed, width, color) stored on UI and package faces, set from a "box" dropdown in both tables, rendered from the attribute everywhere (the mode-line bars, the package previews via ofs, the UI table preview cells), and exported through build-theme.el's --attrs as a proper :box plist (released/pressed → :style *-button; line → :line-width + optional :color). The hardcoded box is gone; mode-line and mode-line-inactive now default to the released-button box that is the Emacs default, so the preview and the export agree.
This also gives the package faces that genuinely use :box a way to represent it — the face audit found several (magit-branch-current/-remote-head, two flycheck list faces, the telega button family, ~15 slack button/dialog faces).
Tests: build-theme gains box-conversion + ui-box-emit ERT tests (24/24); the app-core deep-equal tests account for the new box slot; all 9 browser gates, 20 python, and 55 node tests stay green.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
|
|
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.
|