| Commit message (Collapse) | Author | Age | Files | Lines |
| |
|
|
|
|
| |
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.
|
| | |
|
| |
|
|
| |
Added the uncovered fallback branches in app-core (migrateLegacyFace null input, normalizePkgFace's source fallback chain, mergePackagesInto's null/new-app guards, boxCss shading a relief from the bg when no box color is set) and in colormath (apca's equal-luminance return-0 and low-contrast clamp, isPureEndpointHex). New test-palette-generator-core.mjs drives planPaletteGenerator across every scheme, vibe, source mode, and the fill-gaps intents, since those internals are only reachable through the public planner. colormath branch 96 -> 99%, palette-generator-core funcs 97 -> 100%, node suite 237 tests. The remaining gaps are the deep palette-column edge branches, deferred as diminishing returns on already line-covered code.
|
| |
|
|
| |
cssWeight, faceDecoration, boxCss, and faceCss moved into app-core in the CSS-builder refactor but had no node tests, leaving app-core at 96% line / 95% funcs. Added Normal/Boundary/Error cases for all four: every weight name plus the fallbacks, underline/strike/both/neither decoration, line/released/pressed boxes with and without color and width plus the no-color fallback, and faceCss's background/noBg/fontSize/box-order assembly. app-core is now 100% line; the node suite is 217 tests.
|
| |
|
|
| |
The UI and package tier builders repeated the same "for each (face . obj) entry, build attrs, emit a non-empty spec" loop. Both now call one build-theme/--specs-from-entries helper; the package builder concatenates each app's specs in order. The syntax builder keeps its own form since it fans one category out to several font-lock faces. The 41 ERT tests stay green and the emitted themes are unchanged.
|
| |
|
|
|
|
| |
generate.py's add_default_palette_colors repeated the same fg/bg/box-color harvest three times (syntax, ui, package faces); it now calls one _harvest_spec_colors helper, preserving the add order so the palette and generated page are byte-identical. default_faces' _build_color_hex and _build_color_names each walked the same faces -> chosen/effective -> foreground/background/distantForeground nesting; both now consume one _iter_color_pairs generator and only differ in their key choice and filter. The rebuilt color maps match the originals exactly.
I left the lower-value generate.py items (a build() wrapper, dict-driven fill_data) and the capture/face-coverage script dedups for later: they touch import-time behavior or scripts the suite doesn't run, so they want their own verification rather than riding this change.
|
| |
|
|
| |
The bespoke apps were listed twice: generate.py's _BESPOKE_APPS (key, label, preview, faces, prefix, seed) and app_inventory's BESPOKE_APPS set of keys, which had drifted (it carried both "org" and "org-mode"). The spec list now lives in face_data.py as BESPOKE_APP_SPECS, beside the FACES/SEED constants it references. generate.py builds APPS from it, and app_inventory derives its exclusion set from the same keys plus an explicit "org" alias of the "org-mode" app. Adding a bespoke app is now one row. APPS order and the generated page are unchanged.
|
| |
|
|
| |
The single-spec seed now emits package-face keys in the spec's order rather than the old hand-written order, so the embedded package JSON reorders some keys. Values are identical (the APPS data parses equal; the page normalizes face key order on import), and check-generated requires the committed page to match the generator output.
|
| |
|
|
| |
STYLE_DEFAULTS, default_faces.seed, and capture-default-faces' ATTRS each hand-listed the face attributes, so adding one meant editing three places in step. face_specs.FACE_ATTRS is now one row per attribute carrying the model key, default, capture keyword, snapshot field, and seed transform kind. STYLE_DEFAULTS derives from it (same keys, order, and values), seed iterates it applying the per-kind extraction, and the capture probe map derives its emacs-attr to snapshot-field pairs from it. The snapshot-to-model translation stays genuinely per-attribute (colors prefer the Hex variant, flags become objects, weight/slant are value-narrowed), but the attribute list itself is now single-sourced. Verified byte-identical: STYLE_DEFAULTS and ATTRS match the old literals, the generated page is unchanged, and the suite is green. app-core.js mirrors this list by hand since the page is a separate runtime.
|
| |
|
|
|
|
| |
vibeChroma was a ten-branch if-ladder of magic chroma constants; it's now a [base, range] lookup table, so a vibe is one row to read or tune. resolveSyntaxFg and resolveUiAttr each hand-rolled the same cycle-guarded inherit walk; both now call one walkInheritChain helper that takes the parent and value functions. effResolve keeps its own recursive form since it double-indexes through the package map.
I left the palette-actions splice helpers (replacePaletteEntries, withCfg) and the paletteGroups dedup for a later pass: they mutate the live palette and are only browser-gate covered, so they want their own careful change rather than riding this one.
|
| |
|
|
| |
mkBoxControl duplicated the whole cluster/dropdown/paint/syncLocked machinery that mkLineStyleControl already provides for underline/strike/overline; it differed only in that its state object carries a width. I gave mkLineStyleControl an optional toState builder and reduced mkBoxControl to a wrapper that supplies it. The clear/reset tier functions already delegate their shared loop to clearUnlockedRows, so they were left as is. The buildTable/buildUITable/buildPkgTable skeleton merge is deferred: their shared control/expander/detail-editor/CSS machinery is now all extracted, and what remains is genuine per-tier column divergence that the browser gates pin by exact cell position.
|
| |
|
|
| |
The ~28 renderXxxPreview functions plus ofs/os/previewLines were ~460 lines of bespoke sample content sitting in the middle of the controller. I moved them to a new previews.js, spliced into the page through a PREVIEWS_J token the same way the other inlined libs are, and left the PACKAGE_PREVIEWS registry and dispatcher in app.js. app.js drops from 1233 to 759 lines, and the sample data now lives apart from the table/control machinery.
|
| |
|
|
| |
syntaxStyle, uiCss, and ofs each assembled the same color/background/weight/style/text-decoration/box-shadow string by hand, differing only in how they resolved fg/bg and whether they added a font-size. I promoted one faceCss(face, fg, bg, opts) plus cssWeight, boxCss, and a faceDecoration helper into app-core (all pure, no DOM), and reduced the three builders to thin wrappers that resolve fg/bg and call it. styleEx and paintUI now use the promoted cssWeight/boxCss too. udeco keeps its own untrimmed decoration form, so it stays in app.js.
|
| |
|
|
| |
oklchOf and isPureEndpointHex were each defined identically in app-core.js and palette-generator-core.js, and hueOfHex inlined oklchOf's body a third time. I moved both helpers into colormath.js, which already owns the primitives they call, and had the two consumers import them. hueOfHex now calls oklchOf instead of re-deriving it.
|
| |
|
|
|
|
| |
I replaced the two hand-kept attribute lists in normalizePkgFace and packagesForExport with a single faceAttrs() table. Each row carries the default, whether the value resolves through the palette, and the export rule, so adding a face attribute is one row instead of an edit in two places kept in step by hand.
faceAttrs is a hoisted function, not a const. The inlined page calls normalizePkgFace at top level (seedPkgmap) before the table's source position, where a const would sit in its temporal dead zone.
|
| |
|
|
|
|
| |
hl-line inherits highlight, mode-line-inactive inherits mode-line, and the
dashboard title gains :height 1.15. WIP.json is reserialized under the
expanded face model added this session.
|
| |
|
|
| |
The defface-only pass caught end-of-file to stop reading each file. Any other reader error propagated out, aborting the whole pass and dropping every face in every later file. It now catches any error and stops that one file instead.
|
| |
|
|
|
|
|
|
| |
Re-capturing the default-face snapshot regressed 43 faces to nonexistent, transient and dirvish among them. The capture loaded each package file whole with a bare `load`, and transient 0.12.0 now requires cond-let, which isn't on the batch load-path. The load failed, the error was swallowed, and every face in the file silently dropped. Any package that gains a dependency would hit the same trap.
I split the capture into two passes. Pass 1 keeps the best-effort full load, the only way to register faces a package builds in a macro or loop rather than a literal defface (rainbow-delimiters depth faces, markdown headers). Pass 2 reads each file and evaluates only its defface forms, so a face whose package can't fully load in batch still registers from its self-contained declaration. Pass 2 runs last so the pristine default spec wins over anything a pass-1 load customized.
The refreshed snapshot now records 137 more faces than before (magit, lsp, git, org, and company defaults the old single pass missed), plus the overline field everywhere. The only two dropped faces are gone upstream: consult-separator was removed from consult, and json-mode is no longer installed.
|
| |
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
|
|
|
|
| |
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.
|
| |
|
|
|
|
|
|
|
|
| |
Each row in the syntax, UI, and package tables gets a "more" toggle that reveals a detail row beneath it, holding the attributes that don't fit a column: distant-fg, family, overline, inverse, and extend. The syntax and UI tables also get inherit and height there, since they have no inline column for those. Packages already do, so their expander leaves them out.
One mkDetailEditor builds the editor and one mkExpander wires the toggle plus the hidden detail row, both shared across the three tables. The detail controls join the row's lock cell, so locking a row disables them too.
Detail rows ride along when a table is sorted: applyTableSort now sorts only the main rows and re-attaches each detail row right after its parent (matched by data-detail-for), so a sort never separates the pair. The new #expandtest browser gate covers the toggle, the field set, the model writes, and the package expander dropping inherit/height.
This is the last editor piece for the face-attribute work. Every attribute the model and emitter support is now reachable from the UI. Full suite green: Python 59, Node 198, 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 extended build-theme's emitter to the full attribute set: family, distant-foreground, a weight and slant range, structured underline (color and wave), overline, strike color, inverse-video, extend, and inherit/height on every tier. It still reads the legacy boolean bold/italic/underline/strike fields, so every committed preset round-trips unchanged.
The emitter is the first piece of widening the studio to all face attributes; the model and UI that produce these fields come next. To keep the change clean I refactored --attrs from nine positional arguments to a single face-spec object and lifted the accessor helpers above their callers.
Added 40 ERT tests covering legacy compatibility, each new attribute, the coercion helpers' edge cases, and an end-to-end round-trip that loads a theme and reads the attributes back off the faces. They run in the theme-studio suite as a new stage.
|
| | |
|
| |
|
|
| |
A newly-loaded package (or a new built-in face) used to fall into emacs-core because grouping is by name-prefix and an unknown prefix matched nothing. Now the fallback routes by where the defface lives: an elpa face becomes its own package bucket, a built-in face a new emacs-general child. So loading a package and running make face-coverage surfaces it as a fresh TODO bucket instead of an orphan in core. Recognized faces still match their family first, and faces.el/frame.el faces stay in emacs-core.
|
| |
|
|
|
|
| |
face-coverage.org was rebuilt by a throwaway /tmp script each time. This makes it reproducible: face-coverage-dump.el dumps every face's name, docstring, and defface file from the live daemon (plus all group docs and package summaries), and face_coverage.py turns that into the tiered worklist (emacs-core / emacs-general / per-package), classifying each face by where its defface lives. make face-coverage regenerates the file; make face-coverage-diff reports the coverage delta against the committed copy.
The dump binds coding-system-for-write so writing the docstring JSON never drops into the interactive coding-system prompt. I validated the builder by regenerating and diffing against the hand-built worklist: headings identical, only the intro and one sharper description differ.
|
| |
|
|
| |
I added a one-line description under each package and subsystem heading, pulled from its Emacs customization-group docstring or elpa package summary, with a parent-group fallback for sub-buckets. 109 of 120 buckets resolved; the rest are single-face buckets the face's own docstring already covers.
|
| |
|
|
| |
I added the missing buckets so every built-in subsystem face and package face leaves emacs-core. abbrev and which-func became emacs-general subsystems; git-gutter, git-commit, twentyfortyeight (2048-game), yasnippet, and edit-indirect became their own package headings. The orphans that were genuinely core (mode-line-active, the tty-menu faces, the line-number tick faces, fixed-pitch-serif) joined the core set. emacs-core is now 74 faces with no orphans left.
|
| |
|
|
| |
I regrouped the worklist into three top-level tiers: emacs-core (the standalone built-in faces), emacs-general (built-in subsystems like org, gnus, erc, diff, and vc, each a nested child), and one heading per third-party elpa package. The tier comes from where each face's defface lives, queried from the running Emacs: /usr/share/emacs is built-in, elpa is a package.
|
| |
|
|
| |
I added each face's Emacs docstring (first line) as the line beneath it, so the worklist reads as what every face is actually for. 1129 of 1293 faces carry one.
|
| |
|
|
|
|
| |
I added a hierarchical worklist of every known face: the live Emacs face-list unioned with everything the studio manages, grouped by package. Each face is marked DONE where the studio themes it, TODO where it doesn't, and each group is DONE, DOING, or TODO by how much of it is covered.
690 of 1293 faces are covered today. The gaps are mostly org, gnus, erc, custom, diff, and the built-in subsystems the package inventory doesn't reach yet.
|
| |
|
|
|
|
|
|
| |
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.
|
| | |
|
| | |
|
| |
|
|
| |
The studio opened with every column's span tints expanded, so the first view was crowded with colors the user hadn't asked to see. I set paletteShowFull to false in initApp, so the palette opens showing one tile per column and the existing arrow control expands the spans. The flip lives in the boot path rather than the module default, so the palette gates keep their full-palette baseline. The two that assert span tiles (#counttest, #paltoggletest) now opt into full mode explicitly. A new #paldefaulttest gate asserts the opening collapsed state.
|
| |
|
|
| |
The picker panel's background (#161412) sat a few shades off the page background (#0d0b0a), so it was hard to tell apart from the page. I gave it the studio's gold accent border (#e8bd30) and lifted the background to #1f1c19, both already used in the toolbar chrome, so the panel reads as a distinct surface. The #pickertest gate asserts the accent border and a per-channel background lift of at least 12 over the page, so the distinction can't quietly regress.
|
| |
|
|
| |
mu4e renders the open message with gnus, so the article-view headers, quote levels, signature, and inline emphasis are all gnus faces, not mu4e ones. gnus ships them as bright greens on a dark background, and the theme had no way to reach them. I added gnus as a bespoke view package: the article-view face set in face_data.py with palette seeds that mirror the mu4e header treatment, a realistic preview (header block, emphasized body, an 11-level quoted reply chain, signature), and a #gnustest gate that asserts every emitted face is a real gnus face. Theming and exporting gnus in the studio retires the green in the live view.
|
| |
|
|
| |
The old mu4e preview was cramped and referenced faces that aren't in the mu4e inventory (mu4e-moved-face, mu4e-attach-number-face, mu4e-cited-1..7, mu4e-compose-header-face), so those rendered unthemed. I rebuilt it as a realistic mu4e screen: a status bar, a column header, and one row per message state (unread, replied, flagged, forwarded, draft, trashed, related) with the current line on the highlight background, then a compact message view and the compose separator. It now exercises all 27 mu4e inventory faces and only those. The new #mupreviewtest gate asserts every data-face is a real mu4e face.
|
| |
|
|
| |
markdown-mode fell back to the generic preview (bare face names). I added renderMarkdownPreview, a realistic README that exercises the markdown faces in context: front matter, headers, bold/italic, inline and fenced code, links, lists and checkboxes, a blockquote with a footnote, a table, strikethrough, highlight, math, and inline HTML. A PREVIEW_KEYS map in app_inventory routes markdown-mode to the renderer, and the #mdtest gate checks every data-face it emits is a real markdown face.
|
| |
|
|
| |
The generic .pkgbar button rule outweighed my .viewnav rule on specificity, so the arrows rendered at default button size. Scoping the rule under .pkgbar wins the cascade and restores the compact arrow size.
|
| |
|
|
| |
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.
|
| |
|
|
| |
The contrast column showed "5.4 PASS". The number's color already encodes the tier (green AAA, grey AA, red fail), so the PASS/FAIL word was redundant. I dropped it and put the WCAG meaning in the cell's hover via a pure contrastTitle helper. crHtml now renders just the colored number. verdictFor stays for the covered-overlay worst-case readout, which is unchanged.
|
| |
|
|
| |
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 warning box under the palette wasn't useful there; the same ΔE info is reachable per-chip via the nearest-ΔE tooltip and inline contrast. Remove renderPaletteWarnings, the #palwarn element, its CSS, and the #deltatest gate. paletteWarnings still runs for the per-chip nearest distance.
|
| |
|
|
| |
The literal {bg:MAP['bg'],fg:MAP['p']} repeated 32 times across app.js, palette-actions.js, and the browser gates. Replace it with a groundPair() helper. Named groundPair, not ground, to avoid colliding with the local ground bindings destructured from columnsFromPalette. No behavior change; node tests and browser gates are the safety net.
|
| |
|
|
|
|
|
|
|
|
| |
- 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.
|
| |
|
|
| |
I added paletteUsages, which enumerates every place a color is assigned, grouped by view area (the view dropdown's names: color/code assignments, ui faces, each package app) and the element within it. renderPalette builds the per-area scopes once and appends the list to each used tile's hover title, under the existing name/hex/nearest-deltaE line. Node tests and a #usagetest gate cover it.
|