aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* chore(todo): close Phase 4a OKLCH editor taskCraig Jennings16 hours1-2/+2
|
* feat(theme-studio): add an OKLCH edit mode to the pickerCraig Jennings16 hours2-8/+126
| | | | | | | | Perceptual-metrics Phase 4a. The picker gains an edit-model toggle (HSV / OKLCH) held in its own pkModel state, orthogonal to the existing AA/AAA contrast mask (pkMode). The two never share state: one is how you edit the color, the other is what constraint you mask. In OKLCH mode the picker shows L, C, and H as paired range and number inputs that drive the color through oklch2hex, updating the hex field, swatch, readouts, and the HSV cursor. When the requested chroma is out of sRGB gamut the dials snap to the reachable color and a "chroma clamped to sRGB" line appears. HSV stays the default, and the SV square keeps editing in HSV (the C×L plane is Phase 4b). An SV drag in OKLCH mode refreshes the dials so the two surfaces stay consistent. A #oklchtest headless guard asserts switching to OKLCH preserves the color, toggling the mask leaves pkModel alone, switching the model leaves pkMode alone, the dials drive the color to a known OKLCH target, and an out-of-gamut chroma raises the clamp status.
* chore(todo): close Phase 3 palette ΔE warnings taskCraig Jennings17 hours1-2/+2
|
* feat(theme-studio): warn on too-similar palette colors by ΔECraig Jennings17 hours2-0/+78
| | | | | | Perceptual-metrics Phase 3. renderPalette now runs a pairwise OKLab ΔE over the palette and warns on any pair below the named DELTAE_MIN threshold (0.02). The warning lists the closest pairs first, caps at five, and appends "and N more" so a noisy palette never hides the count. Each chip's tooltip gains its nearest-neighbor ΔE. paletteDeltas computes the pairs and the per-color nearest distance in one pass, feeding both the chip titles and the warning list. Palette names go through esc before they reach the warning markup. A #deltatest headless guard asserts a near-identical pair fires and names itself, a spread palette stays quiet, and a tight cluster caps at five in ascending order with the overflow suffix.
* fix(theme-studio): lay out picker OKLCH/APCA readouts in two columnsCraig Jennings17 hours2-2/+2
| | | | The .pinfo2 row had the font and margin but not the flex space-between the .pinfo row above it uses, so the OKLCH and APCA spans ran together as "90°APCA Lc -39". With space-between the OKLCH coordinates sit under the hex and the APCA Lc sits under the WCAG ratio, matching the row above.
* chore(todo): close Phase 2 picker readouts taskCraig Jennings18 hours1-2/+2
|
* feat(theme-studio): show OKLCH and APCA readouts in the pickerCraig Jennings18 hours3-4/+41
| | | | | | | | Perceptual-metrics Phase 2. The picker now shows a second readout row under the WCAG ratio: the OKLCH coordinates (L, C, hue°) and the signed APCA Lc against the ground color. WCAG and all three contrast tables are untouched, so APCA stays picker-only for v1. APCA Lc carries its sign convention in a tooltip and in the README: positive is dark text on a light background, negative is light text on a dark background, so a light color on dupre's dark ground reads negative. pkReadout drives the new spans from the inlined colormath functions, and a #readouttest headless guard loads dupre-blue and asserts the spans match both the live computation and the known OKLCH reference, with WCAG unchanged.
* chore(todo): close colormath integration phase taskCraig Jennings18 hours1-2/+2
|
* feat(theme-studio): inline colormath.js, migrate WCAG/HSV helpersCraig Jennings18 hours4-19/+317
| | | | | | | | Perceptual-metrics Phase 1. generate.py inlines the colormath.js body into the page script, stripping the ES-module export so one source feeds both the browser and the Node tests. The page's own lin, rl, contrast, rating, hsv2rgb, rgb2hsv, hex2rgb, and rgb2hex copies move into colormath.js. normHex, textOn, and ratingColor stay in the page as UI-boundary helpers. rl now reuses colormath's canonical lin (0.04045 cutoff) instead of the old 0.03928 form. The two are byte-identical on every #rrggbb: no 8-bit channel falls between the cutoffs (10/255 = 0.0392, 11/255 = 0.0431), confirmed over 200k random pairs with zero contrast change and no AA/AAA flips. test-colormath.mjs adds Normal/Boundary/Error cases for the migrated helpers, a seeded hsv-rgb round-trip property test, and an inline-integrity check that the generated page carries the colormath.js body verbatim, so the inlined copy and the tested module can't drift.
* feat(theme-studio): add colormath.js perceptual color coreCraig Jennings18 hours2-0/+189
| | | | colormath.js is the pure color-math module both theme-studio features need: OKLab/OKLCH conversions, oklch2hex with a binary-search gamut clamp, APCA (APCA-W3 0.1.9), and deltaE-OK. It's tested directly in Node (test-colormath.mjs under node --test) against the spec's fixtures (OKLab anchors, the red and dupre-blue OKLCH values, APCA at 106.0 and -107.9, the clamp invariants), at 100% line and 90% branch coverage. Next: generate.py inlines it and the existing rl/contrast/hsv helpers move in.
* docs(theme-studio): add seeding-engine specCraig Jennings18 hours2-2/+362
| | | | The seeding engine turns the color guide's seed table into executable defaults: a role-to-treatment table, a face-to-role map per tier (syntax, UI, org), and a pure seed() that opens the tool guide-correct and reseeds dupre-revised to the compact mapping. v1 generates the shades with OKLCH, reusing the perceptual-metrics colormath.js core, so it sequences after that feature's Phase 1. todo.org carries the two implementation phases.
* chore(todo): track theme-studio guide-support feature specsCraig Jennings19 hours1-0/+6
|
* docs(theme-studio): add color-assignment guideCraig Jennings19 hours2-0/+477
| | | | theme-coloring-guide.org is the design philosophy behind the tool, organized from principles out: seven laws, a role-to-treatment seed table, and three tiers (syntax, UI faces, org packages) as that table projected onto Emacs faces. It documents a shade budget per hue family, a functional signal-color convention table (not an emotion table), color-vision rules, and Emacs face specifics, and closes with canonical references. The README points to it, and dupre is the worked example.
* docs(theme-studio): add perceptual color metrics specCraig Jennings22 hours2-1/+609
| | | | The spec adds OKLCH editing, perceptual-lightness and APCA readouts, and a pairwise ΔE distinguishability check to the theme-studio, so it can build deliberately low-contrast themes by metric instead of by eye. The testing strategy extracts the color math into a Node-unit-tested colormath.js core, with the browser hash tests reduced to UI wiring and coverage measured on that core. todo.org carries the five implementation phases and the manual-validation checklist.
* fix(theme-studio): open the picker crosshair on the current colorCraig Jennings25 hours2-2/+4
| | | | openPicker positioned the crosshair while the picker was still display:none, so its client dimensions read 0 and the crosshair pinned to the top-left corner instead of the shown color. I moved display:block ahead of paintPicker so the element has layout when its size is read, then added a headless #cursortest that opens the picker on a saturated color and checks the crosshair lands off-corner.
* feat(theme-studio): add dupre theme design exportsCraig Jennings26 hours2-0/+10586
| | | | Saved theme-studio exports for the dupre palette. dupre.json holds the base palette, UI, and syntax assignments; dupre-revised.json adds the full per-package face set across 51 packages. build-theme.el converts either into a loadable deftheme.
* chore(emacs): sync abbrev_defsCraig Jennings26 hours1-2/+3
|
* chore(todo): drop the F8-in-ghostel manual check (confirmed passing)Craig Jennings26 hours1-5/+0
|
* feat(term): open the agenda with F8 from inside ghostel buffersCraig Jennings26 hours3-5/+52
| | | | F8 is bound globally to cj/main-agenda-display, but ghostel's semi-char mode forwarded it to the terminal program, so it did nothing inside a ghostel buffer. I added F8 to ghostel-keymap-exceptions and rebuilt the semi-char map, which lets it fall through to the global map, the path F10 and the F9 family already take.
* chore(pearl): default to the deepsat workspace againCraig Jennings26 hours1-4/+4
| | | | The dogfooding window for the personal "craigjennings" workspace has closed, so work is primary again. Switch per-session at runtime with M-x pearl-switch-account.
* fix(theme-studio): make a face-row click visibly flash its preview elementCraig Jennings29 hours2-10/+16
| | | | Clicking a UI or package face already called the flash, but two things hid it: the flashtok animation was scoped to #codepre and .ex cells, so the class landed on the mock-frame and package-preview spans without ever animating; and the flash never scrolled its target into view, so an element below the fold of a preview's scroll box flashed unseen. The animation is now global, and the flash scrolls its element into view (the first of several, via a new flashEls helper). Clicking a face in either tier now lands the viewport on the matching preview element and lights it up.
* refactor(theme-studio): rename theme-selector to theme-studioCraig Jennings29 hours10-46/+46
| | | | The tool authors themes from scratch -- palette, faces across every tier, live preview, export to a loadable deftheme. It never selects among existing themes, so "selector" mis-described it. Renamed the directory, the generated HTML and its title, the design spec, and every reference in the code, README, tests, and todo. No behavior change.
* feat(theme-selector): sortable package and UI face tablesCraig Jennings31 hours2-4/+24
| | | | The package table (face, fg, bg, inherit, size, contrast) and the UI table (face, foreground, background) now sort on a header click, like the code/color assignments table. A generic comparator reads a select value, a numeric input, or cell text (numeric when it leads with a number, so size and contrast sort by value). The sort is remembered per table and re-applied after a rebuild, so editing a face no longer resets the order.
* feat(theme-selector): remember the imported/saved file so save overwrites itCraig Jennings31 hours2-16/+34
| | | | Import now goes through showOpenFilePicker and keeps the returned file handle, and the rename handler no longer drops the handle on every keystroke. Once a theme has been imported or saved, save writes back to that same file (the first write asks once for permission, then overwrites silently); the save button title reflects whether it will overwrite or prompt for a location. Browsers without the File System Access API fall back to the hidden file input and a download-style save, as before.
* feat(theme-selector): bold/italic/underline/strike controls on the UI face tableCraig Jennings31 hours2-15/+21
| | | | The UI face table gains a style column with the same B/I/U/S toggle buttons the package table has, so built-in UI faces reach parity. Each toggle updates the per-face preview cell and the live mock buffer (mode-line, minibuffer-prompt, link, error/warning/success now reflect weight, slant, and decoration). The link face is seeded underlined to match the built-in default, and the converter already reads these fields off the ui objects, so they export and convert without further change.
* docs(theme-selector): document underline and strike in the theme.json contractCraig Jennings31 hours2-4/+7
| | | | Record the two new face booleans in the README contract and the spec's state-and-export-policy block, including that the converter writes them as :underline t and :strike-through t.
* feat(theme-selector): converter writes :underline and :strike-throughCraig Jennings31 hours2-14/+40
| | | | build-theme/--attrs takes underline and strike flags and emits :underline t and :strike-through t in canonical order (after slant, before height). The UI and package spec builders read the two new fields off each face object; syntax and default faces pass nil since they never carry them. Two new ERT tests plus updated ordering cases; an end-to-end convert confirms a shr-link face round-trips to :underline t and shr-strike-through to :strike-through t. 22/22 green.
* feat(theme-selector): underline and strikethrough on package facesCraig Jennings31 hours2-22/+22
| | | | Package faces gain underline and strike toggles alongside bold and italic. Each face object carries the two new booleans, the face table shows U and S buttons (the S button itself renders struck through, U underlined), the preview applies text-decoration so links underline and strike faces strike for real, and theme.json round-trips both fields. Link and strike-through faces across shr, mu4e, erc, slack, and telega are seeded with the right decoration so they look correct by default. The schema self-test still passes, so old exports without the fields import unchanged.
* feat(theme-selector): split C and C++ code samples, add JavaCraig Jennings31 hours3-13/+65
| | | | The combined C/C++ preview is now a dedicated C sample and a dedicated C++ sample, plus a new Java sample. Each is tokenized to exercise every syntax category the language actually has: C++ hits all 19 (including [[nodiscard]] attributes, a std::regex raw string, and a Doxygen doc comment); C hits 18 (no regex literal exists in C); Java hits 17 (no preprocessor and no global builtins). C also gained a doc comment and a __attribute__ decorator over the old combined sample.
* feat(theme-selector): bespoke shr preview (themes nov, eww, and HTML mail)Craig Jennings31 hours3-8/+37
| | | | nov defines no faces of its own; it renders EPUBs entirely through shr, the built-in HTML renderer also behind eww, elfeed's article view, and HTML mail. Rather than a hollow nov entry, this exposes shr directly: a rendered-document preview (chapter title, section headings, body text, links, inline code, mark, strike-through, superscript, abbreviation, image slice) covering all 15 shr faces. Theming it themes every HTML reader at once. The body font (variable-pitch) stays a font-config concern and body color follows the default face, so nov needs nothing beyond shr.
* feat(theme-selector): bespoke previews for slack and telegaCraig Jennings31 hours3-8/+136
| | | | slack covers all 57 faces (a channel buffer with mrkdwn formatting, mentions, reactions, attachments, block-kit elements, dialogs, user and search rows, and modeline indicators). telega covers all 91 (root and chat-list rows, message entities, reactions, the full box-button matrix, describe/enckey/palette samplers, and webpage rendering). README updated to nineteen bespoke previews. Each verified by a headless DOM dump.
* feat(theme-selector): bespoke previews for calibredb, erc, org-drill, ↵Craig Jennings31 hours2-5/+121
| | | | | | org-noter, signel, pearl calibredb (28 faces: a search-header bar plus a library listing and a detail view), erc (31: a full IRC channel buffer covering nicks, prefixes, pals/fools/dangerous hosts, actions, DMs, formatting, and the keep-place indicators), org-drill (3: a cloze flashcard), org-noter (2: notes-exist markers), signel (4: a Signal chat), and pearl (6: a ticket with modified-field states). signel and pearl are personal packages absent from the generated inventory, so their face lists come from the package sources. Each preview covers every face, verified by a headless DOM dump.
* chore(todo): ticket evaluating the jamescherti essential-packages listCraig Jennings32 hours1-0/+3
|
* feat(theme-selector): bespoke previews for lsp-mode, git-gutter, flycheck, ↵Craig Jennings32 hours3-13/+184
| | | | | | dired, dirvish Five more apps leave the generic face-name list for full bespoke previews: lsp-mode (signature, inlay hints, read/write/textual highlights, rename, install) covering all 14 faces; git-gutter all 5 (its face names carry colons, which survive the data-face attribute and flash selectors); flycheck all 20 (a diagnostic line plus an error-list buffer); dired all 12 (built-in, so curated from the daemon rather than the inventory); dirvish all 38 (attribute columns, vc states, media info, proc, narrow). Every face renders a clickable preview element, verified by a headless DOM dump per app.
* feat(theme-selector): bespoke previews for ghostel, mu4e, and dashboardCraig Jennings34 hours4-10/+137
| | | | These three no longer fall to the generic face-name list. ghostel renders a mock terminal covering all 19 faces (the 16 ANSI colors, default, and the fake cursor). mu4e renders a headers list, message view, and compose stub covering all 37 of the faces the dupre theme themes; mu4e is absent from the generated inventory, so its face list is curated. dashboard covers all 8. Every face has a real preview element, so the click-to-flash linking works both directions with no fallback.
* feat(theme-selector): near-complete face coverage in org/magit/elfeed previewsCraig Jennings34 hours2-42/+142
| | | | The org preview now exercises 83 of 88 org faces (document plus agenda view); the five it skips are non-visual (org-hide, org-indent, org-clock-overlay, org-default, org-date-selected). Magit covers 97 of 98 across a status buffer plus labeled sampler rows for the long tail (diff variants, reflog, rebase sequence, bisect, blame, signatures, cherry). Elfeed now covers all 13. So clicking a face row flashes a real preview element for nearly every face instead of falling back to the row cell.
* feat(theme-selector): bidirectional flash-link for UI and package facesCraig Jennings34 hours3-12/+21
| | | | Clicking a face row now flashes that face in the live-buffer and package previews, and clicking an element in either preview flashes its table row. The syntax tier already did this. UI faces had only preview-to-row, and package faces had neither. The package preview spans and table rows now carry data-face, reusing the same delegated-click handler the mock frame uses. When a face isn't shown in a bespoke preview, the row falls back to flashing its own cell. A fuller org preview that gives every org row a real target is filed as a separate task.
* style(theme-selector): 12pt previews and assignment-table refinementsCraig Jennings34 hours2-26/+26
| | | | Every preview surface (code samples, live buffer, package previews) now renders at 12pt instead of the earlier mix of 10, 15, and 19px. The code/color assignments table leads with the elements column before color. I dropped the redundant normal style button: toggling bold or italic off already returns a row to normal.
* style(theme-selector): unify all previews to a 10pt point sizeCraig Jennings34 hours2-12/+12
| | | | The code samples rendered at 19px while the live-buffer and package previews sat at 15px. All preview surfaces now share 10pt so sizes can be judged consistently. Package-face height steppers stay em-relative to the new base.
* feat(theme-selector): convert theme.json into a loadable defthemeCraig Jennings35 hours4-7/+558
| | | | | | | | | | build-theme.el is the last link in the theme-selector pipeline: a theme.json export becomes a single self-contained themes/<name>-theme.el. All four tiers convert: default from assignments.bg/.p, the syntax categories to their font-lock/tree-sitter faces with the bold/italic sets applied, UI passthrough, and package faces with :inherit/:height/weight/slant. The output is a flat generated deftheme, not the palette/faces/theme trio the hand-authored dupre ships. A theme.json carries resolved per-face hex, not dupre's semantic-mapping layer, so a flat deftheme is the faithful output and never clobbers the curated dupre files. I omitted the dec (decorator) key: Emacs has no dedicated decorator face and renders decorators with font-lock-type-face, which the type key already owns, so coloring dec independently would clobber types. Decorators follow the type color, as they do in stock Emacs. 20 ERT tests cover the attribute builder, each tier, the dec omission, and an end-to-end convert-and-load with a WCAG-AA assertion on the round-tripped default.
* chore(todo): ticket the theme-selector theme.json to dupre-*.el converterCraig Jennings35 hours1-0/+17
| | | | The last link in the tier-3 pipeline, with pointers to the theme.json contract, the existing dupre theme files, and the Elisp/testing conventions so a fresh session can start from the task alone.
* test(theme-selector): cover the tier-3 acceptance criteria in the self-testCraig Jennings35 hours3-7/+20
| | | | | | I extended the hash-guarded self-test harness to assert the tier-3 acceptance criteria against the real emitted code, run in headless Chrome: old-JSON import with no packages key, the full package round-trip (fg, bg, bold, italic, inherit, height, source), cleared-state export, unknown-package preservation, and inheritance-cycle termination. All pass. The two DOM-coupled regressions are handled structurally rather than in the harness: updateColor remaps PKGMAP when a palette color is edited, and PKGMAP stores hexes so a deleted palette color leaves package references in the recoverable "(gone)" state. This closes the tier-3 package-faces milestone: all seven phases plus the test surface have landed.
* docs(theme-selector): rewrite README for the full tool (tier-3 phase 7)Craig Jennings35 hours2-24/+91
| | | | I rewrote the README to cover what the tool actually is now: the three face tiers (syntax, UI, package) plus the palette, the in-page color picker with its AA/AAA legibility mask, the package-faces section with bespoke org/magit/elfeed previews and the generic fallback, modeled inheritance and relative height (with the note that the font family stays in font-config.el), the theme.json packages schema with inherit/height/source, export-versus-save, and the inventory-refresh command with its loaded-config dependency. It also documents that theme-selector.html is generated.
* feat(theme-selector): generated all-package inventory (tier-3 phase 6)Craig Jennings35 hours5-3/+768
| | | | | | I added the hybrid inventory. build-inventory.el, loaded into a running Emacs, queries every installed package's faces grouped by the package that defines them and writes package-inventory.json. generate.py embeds that file and merges each package into the app dropdown as an editable generic app, leaving the bespoke org, magit, and elfeed untouched. The dropdown now reaches 40 apps: the three bespoke plus 37 inventory packages (643 faces), so any installed package can be themed against the palette with the generic preview. The inventory is a committed data artifact refreshed by reloading the .el, never browser-side discovery, matching the spec's hybrid-and-split decision.
* feat(theme-selector): magit and elfeed bespoke previews (tier-3 phase 5)Craig Jennings35 hours3-6/+70
| | | | I added renderMagitPreview and renderElfeedPreview. The magit one is a status buffer: head and branch lines, an untracked section, a diff hunk with context, removed, and added lines, and recent commits with hashes, authors, and a keyword and tag. The elfeed one is a search list: the filter line, dated entries with feed, unread and read titles, and tags, plus log lines colored by level. The preview pane dispatches to each app's bespoke renderer, the generic fallback covers everything else, and the pane label now names the app and says whether the preview is bespoke or generic.
* feat(theme-selector): org-mode bespoke preview (tier-3 phase 4)Craig Jennings35 hours3-4/+62
| | | | I added renderOrgPreview, a mock org document painted live from the org package faces. It exercises the prominent ones in context: document title, headings with their heights, a TODO and a DONE, tags, a scheduled timestamp, a property drawer, inline code and verbatim, a link, a checkbox, a quote, a src block with begin and end lines, and a header-row table. The preview pane now dispatches on the app's preview key, so org-mode gets this bespoke document and every other app keeps the generic face-name list until its own preview lands. It rebuilds on any package-face or palette edit.
* feat(theme-selector): package-faces table UI (tier-3 phase 3)Craig Jennings35 hours3-16/+114
| | | | | | I added the package faces section: an application selector for org-mode, magit, and elfeed, and a face table for the chosen app with a foreground and background dropdown, bold and italic toggles, an inherit dropdown (base faces plus the app's own faces), a relative-height stepper, a live contrast readout on the effective inherit-resolved color, and a per-face reset, plus a per-app reset and a text filter for the large sets. The right pane shows a generic preview, each face name in its own resolved colors, which the bespoke org/magit/elfeed previews replace in the next phases. The fg/bg dropdown is now a shared colorDropdown helper that the ui-faces table also uses, so there's no forked control. Palette edits propagate to package faces, and import and export carry them through.
* feat(theme-selector): curated org/magit/elfeed face data (tier-3 phase 2)Craig Jennings35 hours3-25/+139
| | | | | | I filled the APPS registry with the complete own-defface sets: 88 org faces including org-agenda, 98 magit, and 13 elfeed, built from embedded face-name lists plus a curated seed-color map. Prominent faces are seeded with palette colors, heading heights, and the fixed-pitch inherits that keep code and tables monospace under variable-pitch prose. The long tail seeds to the default foreground for the user to tune. Org headings carry their heights, agenda dates and deadlines are colored by role, and magit's diff, branch, and section faces map to the palette's greens, reds, and blues. No UI yet. That's phase 3, and the schema self-test still passes while seeding all 199 faces.
* feat(theme-selector): full category coverage, elements rename, visible tile ↵Craig Jennings35 hours3-15/+18
| | | | | | | | borders I renamed the assignments column "category" to "elements" and gave every language sample a token for each category that applies to it, so clicking an element name flashes real code in any language: Python gained a regexp, Elisp/Go/TypeScript/C each a builtin, and Go and Shell an escape. The categories a language genuinely lacks (Elisp has no preprocessor, Shell no types) fall back to flashing the example cell. The save area header is now "export, import, and save", and the palette tiles and the picker swatch carry a visible border so a tile that matches the page background still reads.
* feat(theme-selector): category-flash, full-height mock, contrast-limit maskingCraig Jennings36 hours2-14/+44
| | | | I made three preview and picker fixes. Clicking a category name in the code/color assignments table now flashes the matching tokens in the code sample, the reverse of clicking a token to flash its row, and falls back to flashing the example cell when the language has no token of that category. The live buffer preview now syncs its height to the ui-faces table so the two end together. The picker gains an any / AA+ / AAA limit: the saturation-value square masks the region that fails the chosen contrast against the background, and the reuse chips dim the colors that don't pass, so it's hard to land on an illegible color.