v0.14.2

Released May 20, 2026 · All releases

Curated syntax preset lineup (Phase 1). The v0.14.2 milestone implements — imports six widely-recognised syntax palettes (Dracula, Solarized, Catppuccin, Tokyo Night, One Dark, Gruvbox) as first-party refrakt presets, following the pattern established by Nord in .

Changes

  • Curated syntax preset lineup phase 1 (SPEC-057) + tint-mode override fix on preset-extending tints + jsonc highlighter language.
  • Six imported syntax palettes. New @refrakt-md/lumina/presets/{dracula,solarized,catppuccin,tokyo-night,one-dark,gruvbox} ship the six most widely-recognised community palettes as first-party presets, following the pattern established by Nord in v0.14.1. Dracula and One Dark are dark-only; Solarized, Catppuccin, Tokyo Night, and Gruvbox carry both light and dark canvases. Each preset uses the SPEC-056 mechanism unchanged — a ThemeTokensConfig module with extended SyntaxTokens roles, opt-in color.code.* for canvas-claiming palettes, and live doc pages at /themes/<name> rendered through the scoped-tint mechanism. Application work only; no architectural changes.
  • Tint-mode override fix on preset-extending tints. When a tint extended a preset module path (e.g. tint="nord" → extends: '@refrakt-md/lumina/presets/nord'), only the build-time scoped tint stylesheet knew about the preset and emitted --rf-color-* directly under [data-tint="nord"]. The runtime engine never saw the projected chrome accents because presetMap wasn't plumbed through mergeThemeConfig → resolveTintExtends, so no inline --tint-* styles were emitted on tinted elements — tint.css's [data-color-scheme][data-tint] selectors collapsed to the colour scheme's neutral defaults regardless of the preset. presetMap now threads from the SvelteKit loader path through assembleThemeConfig and mergeThemeConfig into resolveTintExtends, so preset chrome accents land in TintTokens shape and the engine emits them as inline --tint-* styles. The static scoped tint stylesheet keeps emitting --rf-color-*; inline styles override on the same element exactly as token-stylesheet.ts already anticipated.
  • jsonc added to pre-loaded highlighter languages. Config snippets across the preset doc pages (and other refrakt docs) use ```jsonc to fence JSON-with-comments. jsonc was intentional but not in Shiki's DEFAULT_LANGS, so the highlighter silently fell back to plain text. One-line add — Shiki ships jsonc as a bundled language.

Curated syntax preset lineup — Phase 1

SPEC-057shipped

Curated syntax preset lineup — Phase 1

Ship six widely-recognised, well-licensed syntax palettes as first-party refrakt presets — Dracula, Solarized, Catppuccin, Tokyo Night, One Dark, Gruvbox — following the pattern established by WORK-220 (Nord) under SPEC-056. Each palette becomes a ThemeTokensConfig module under packages/lumina/src/presets/, gets a documentation page under /themes/, and is registered in the refrakt docs site's theme.tints for live showcase rendering. No architectural changes — this spec is the application of SPEC-056 to a curated lineup.

This is Phase 1 of a longer roadmap. Phase 2 candidates (GitHub Light/Dark, Ayu, Rosé Pine, Monokai, Kanagawa, additional Catppuccin and Tokyo Night flavours) are listed in "Out of scope" so the door is open without committing to them now.

themepresetssyntax-highlightingluminacurated-palettes

Problem

SPEC-056 widened the syntax token contract and proved the mechanism on Nord. The lineup at v0.14.1 is:

  • Lumina neutral default — the built-in chrome + neutral syntax everyone gets out of the box.
  • Tideline — Lumina's cream + maritime navy chrome preset.
  • Niwaki — refrakt's own scoped Japanese-garden syntax-only preset.
  • Nord — the first imported third-party palette, integrated (chrome + canvas + foreground).

That's enough to demonstrate the architecture but not enough to satisfy users who want their site to feel like their preferred editor. Most refrakt users picking a theme want to choose from palettes they already know and love, not learn refrakt-internal vocabulary. The architecture is ready; the catalog isn't.

This spec is application work, not architecture work. Each preset is roughly 50–80 lines of ThemeTokensConfig plus a doc page; the implementation pattern is the one validated on Nord. Risk is low; volume is the main cost.

Design Principles

One preset per palette identity, not per flavour. Palettes with explicit light + dark variants from the same author (Solarized, Catppuccin, Gruvbox) ship as a single preset using modes.dark for the dark overlay. Users select the rendering mode via theme.colorScheme or page-level cascade, not by picking a different preset. This keeps the catalog small and matches the "one Nord, one Niwaki" precedent.

Canonical pair only for multi-flavour palettes. Catppuccin has four flavours (Latte / Frappé / Macchiato / Mocha); Tokyo Night has three (Storm / Moon / Day). Phase 1 ships the canonical light+dark pairing per palette (Catppuccin Latte+Mocha, Tokyo Night Day+Storm) and defers the mid-darks to a future milestone if there's demand. The mechanism doesn't preclude additional flavour presets — it just doesn't require them right now.

Integrated, not scoped. Every palette in this lineup ships with a canonical canvas tuned together with its foreground. They claim chrome accents + code surface + syntax, like Nord, not foreground-only, like niwaki. Niwaki remains the only scoped-archetype preset in the catalog.

Attribution is part of the deliverable. Every imported preset module carries an attribution header naming the original author(s), the source URL, and the licence (all six are MIT or equivalent). Every doc page carries the same attribution at the bottom.

No architectural changes. This spec is forbidden from modifying SyntaxTokens, TintDefinition, the scope-eligibility filter, the extended Shiki theme, or the generator. If a palette can't be expressed cleanly under the existing contract, the palette is deferred — not the contract revised.

The curated lineup

Six palettes chosen for distinctive visual identity, clean licensing, breadth of role-split exercise, and diversity of colour temperature. Each entry below names the rationale, the light/dark shape, and a sketch of the role mapping that the work item will refine.

Dracula

Purple/pink/cyan on near-black. Created by Zeno Rocha; one of the most installed editor themes on the planet. MIT licensed (https://github.com/dracula/dracula-theme).

  • Shape: dark-canonical, integrated. No official light variant — would ship as dark-only (the preset's modes.dark carries the canonical values; light mode falls back to a darkened version or matches Dracula's "Alucard" sibling if author permits; otherwise we ship as a dark-only preset and note it).
  • Why include: peak name recognition. Splits keyword (Pink), function (Green), type (Cyan), string (Yellow), constant (Purple) — exercises SPEC-056's extended roles at full fidelity.

Solarized

Light and dark, the same 16 hues from Ethan Schoonover's 2011 palette. MIT licensed (https://github.com/altercation/solarized).

  • Shape: integrated, both modes from the same hue family (the only palette in the lineup designed deliberately around mode-symmetric canvases). Light = base3 canvas + base00 text; dark = base03 canvas + base0 text. Same accents (yellow/orange/red/magenta/violet/blue/cyan/green) in both modes.
  • Why include: historical importance (most-cloned palette ever), and the test case for our "unified light+dark preset" shape. Validates that our modes.dark mechanism handles palettes whose authors want the modes coordinated, not independent.

Catppuccin

Modern soft-pastel family, four flavours. Phase 1 ships Latte (light) + Mocha (dark) as the canonical pair. MIT licensed (https://github.com/catppuccin/catppuccin), unusually well-spec'd palette docs.

  • Shape: integrated, light + dark from the same named-swatch family. Each flavour uses the same role assignments with different absolute values, so the role mapping written for Mocha translates structurally to Latte.
  • Why include: currently very popular; well-documented spec means our mapping can be precise; demonstrates the "canonical pair only" multi-flavour decision. Frappé and Macchiato can be Phase 2 additions if there's demand.

Tokyo Night

Dark blue + magenta + cyan. Created by Enkia. MIT licensed (https://github.com/enkia/tokyo-night-vscode-theme). Phase 1 ships Storm (dark) + Day (light) as the canonical pair.

  • Shape: integrated. Storm uses #1a1b26 canvas; Day uses #e1e2e7. Distinctive role splits — type (cyan) ≠ function (blue) ≠ parameter (orange) ≠ keyword (magenta) ≠ number (orange-distinct-from-constant).
  • Why include: exercises the SPEC-056 widened contract more thoroughly than any other lineup member. If Tokyo Night maps cleanly, the contract works for the maximally split-out palettes too.

One Dark

Atom's signature dark theme. Created by GitHub's Atom team. MIT licensed (https://github.com/atom/atom/tree/master/packages/one-dark-syntax). Phase 1 ships dark only — official "One Light" sibling deferred to Phase 2.

  • Shape: dark-only initially. Blue-grey canvas (#282c34), red/green/yellow/cyan/blue/purple accents.
  • Why include: fills the "modern blue-grey dark" niche distinct from Tokyo Night's saturation. Historical importance — Atom was the first widely-adopted editor with custom theme APIs.

Gruvbox

Warm retro palette. Created by Pavel Pertsev. MIT licensed (https://github.com/morhetz/gruvbox). Phase 1 ships dark medium + light medium as the canonical pair.

  • Shape: integrated, light + dark variants from coordinated hues. Earthy: orange keywords, deep green functions, mustard strings, warm-red diagnostic colour.
  • Why include: the only warm palette in the lineup. Five blue/cool members + one warm gives the catalog visual variety. Gruvbox is also the most "unix terminal heritage" of the lineup — important counterweight to the JS-editor-modern bias of Tokyo Night / Catppuccin.

Authoring Surface

No new authoring surface. Every preset uses the same opt-in mechanism Nord uses:

{
  "site": {
    "theme": {
      "package": "@refrakt-md/lumina",
      "presets": ["@refrakt-md/lumina/presets/dracula"]
    }
  }
}

And the same scoped-tint registration for showcase pages:

{
  "tints": {
    "dracula": { "extends": "@refrakt-md/lumina/presets/dracula" }
  }
}

The refrakt docs site's tints config will gain entries for all six during this milestone so each preset's doc page can use tint="<name>" on palettes and the live preview codegroup.

Doc page structure

Every preset doc page follows the structure validated on Nord:

  1. Title + intro — palette identity, author, integrated-vs-scoped position, attribution sentence
  2. Opt in — config snippet
  3. The palette — two {% palette %} blocks (light + dark, both tint="<preset>") with named swatches
  4. Live preview — the shared TypeScript+JSX snippet wrapped in {% codegroup tint="<preset>" %}
  5. Composing with X — a composition example with at least one other preset
  6. Attribution — full credit + licence

The TypeScript+JSX snippet stays identical across all preset pages so readers can directly compare palettes by toggling between pages without re-reading the example.

Out of scope

  • Architectural changes — see "Design Principles" above. This spec is application work only.
  • GitHub Light / Dark — universal recognition but optimised for "match what you see at github.com" rather than aesthetic choice. Phase 2 candidate.
  • Monokai (original) — historical importance, but the modern incarnation (Monokai Pro) is paid and the brand is fragmented. Easier as Phase 2 once we've established the import pattern more broadly.
  • Ayu, Rosé Pine, Kanagawa — strong palettes, but Phase 1 has to stop somewhere. Easy Phase 2 additions.
  • Additional Catppuccin / Tokyo Night flavours — Frappé, Macchiato, Tokyo Night Moon. The mechanism doesn't preclude them; Phase 2 if there's demand.
  • Per-preset Plugin packages — palettes stay inside @refrakt-md/lumina/presets/ for v1.x. Splitting into per-palette plugin packages is a packaging decision, not a content decision, and is out of scope here.
  • Light-only or dark-only mode locks per preset — the user chooses via theme.colorScheme; presets ship both modes when the source palette has both. No dracula-light / dracula-dark split presets.

Validation

The spec is validated by all six preset doc pages rendering on the refrakt documentation site (whose active preset is niwaki) with their respective palettes correctly scoped to the palette swatches and code preview. Specifically:

  1. Each preset's palette swatches sit on the preset's canonical canvas (not niwaki's).
  2. Each preset's live code preview renders with the preset's syntax tokens and the preset's code-surface canvas.
  3. The surrounding page chrome (header, nav, prose) stays in niwaki on every preset doc page — no leakage.
  4. Each preset composes cleanly with tideline (["tideline", "<preset>"]) — tideline typography survives, preset chrome + syntax wins.
  5. Each preset module ships with attribution in the file header.
  6. The themes catalog landing page (/themes/themes-catalog) lists all six in the "Syntax presets" group (renamed to "Imported syntax presets" if useful — see open question below).
  7. No new tests fail; existing tests stay green. CSS coverage tests still pass for Lumina.
  8. The light-mode + dark-mode toggle on each preset doc page flips both the page chrome and the tinted regions correctly.

Acceptance Criteria

  • Six preset modules added under packages/lumina/src/presets/: dracula.ts, solarized.ts, catppuccin.ts, tokyo-night.ts, one-dark.ts, gruvbox.ts.
  • Each module exports a ThemeTokensConfig with chrome accents, color.code.*, and syntax.* for both light (when applicable) and dark modes via modes.dark. Single-mode palettes (Dracula, One Dark in Phase 1) ship dark-only and that's explicit in the file header.
  • Each module's file header includes attribution: author, source URL, licence.
  • packages/lumina/package.json exports each new preset path (./presets/dracula, etc.).
  • A test file per preset under packages/lumina/test/ mirroring nord-preset.test.ts — verifies structural shape, role-split assertions, code-surface canvas, CSS generation, and composition with tideline / niwaki / Lumina.
  • Six doc pages under site/content/themes/: dracula.md, solarized.md, catppuccin.md, tokyo-night.md, one-dark.md, gruvbox.md. All follow the Nord-page structure.
  • refrakt.config.json sites.main.tints gains six new entries, each extending the corresponding preset module path.
  • site/content/themes/_layout.md "Syntax presets" group lists all six (alongside niwaki and nord).
  • site/content/themes/themes-catalog.md updated to mention the new presets and group them appropriately.
  • Full test suite passes. Site builds clean.

Open questions (defer to PR-time)

  • Group naming — should /themes/_layout.md split "Syntax presets" into "refrakt's own" (niwaki) and "Imported" (nord + Phase 1 lineup)? Or leave one group with eight entries? My lean: one group at this size; revisit at 12+.
  • Theme catalog landing copy — the catalog page currently lists presets one-by-one. At eight syntax presets, a table or grid might read better than a flat list.
  • Composition example variation — should every preset's "Composing with X" section pair with tideline (the same partner) or pick a different partner per preset (niwaki, neutral default, another import)? My lean: every preset gets one tideline example for consistency; one bonus composition pairing if it's interesting.

These don't affect the spec — they're authoring polish that the implementing PR can settle.

Nord preset module

WORK-220done

Nord preset module

Author Lumina's Nord preset at packages/lumina/src/presets/nord.ts — an integrated palette preset that maps the official Nord palette's 16 named hues onto the extended SyntaxTokens contract and sets the code-surface (color.code.*) tokens so opting into Nord produces code blocks on Nord's canonical Polar Night canvas. Nord is the validation case for SPEC-056's three extensions: syntax role widening, optional code-surface claiming, and (via WORK-223) scoped tint projection.

Where niwaki took the scoped "foreground only" position deliberately, Nord takes the integrated "canvas + foreground" position because that's how Nord was designed — its hues were tuned against nord0 specifically, and rendering Nord foreground on Lumina's warm-cream bg misrepresents the palette.

luminapresetsnordsyntax-highlighting

Acceptance Criteria

  • packages/lumina/src/presets/nord.ts exports a ThemeTokensConfig overriding syntax.* and color.code.* (no chrome bg/surface, no fonts, no status, no radius/spacing/shadow — those stay chrome's responsibility)
  • Built to dist/presets/nord.js, exported at ./presets/nord in packages/lumina/package.json
  • Sets at least one extended optional syntax role distinctly from its core fallback (e.g. type ≠ function) — proves the fidelity gain is real, not theoretical
  • Sets color.code.bg, color.code.text, and color.code.border for at least one mode (dark) — proves the canvas-claiming path works end-to-end
  • Light-mode and dark-mode values both supplied via modes.dark.* (Nord is dark-canonical but ships an Aurora-derived light variant — verify the light values against the Nord spec)
  • Each hue in the file references its official Nord swatch name in a comment (e.g. // Frost nord7) for traceability against the upstream palette
  • File header includes attribution: "Derived from the Nord palette by Arctic Ice Studio & Sven Greb, MIT licensed. https://www.nordtheme.com/"
  • A test site (or merge-pipeline test) with presets: ["@refrakt-md/lumina/presets/nord"] against the neutral default renders code blocks with Nord colours on Nord's canvas — verifying both the syntax tokens and code-surface tokens cascade together
  • Composes with tideline — ["tideline", "nord"] renders tideline chrome with Nord syntax + Nord code surface. The rest of the page (body bg, surfaces, buttons, borders) stays in tideline; only the code surface flips to Nord
  • No CSS coverage regressions in npx vitest run packages/lumina/test/css-coverage.test.ts

Approach

Read the official Nord palette spec (https://www.nordtheme.com/docs/colors-and-palettes) and Nord's own syntax highlighting documentation (which roles each Frost/Aurora hue is intended for, and which Polar Night/Snow Storm hues are intended for canvas/text). Then map onto refrakt's extended contract:

Rough starting mapping for syntax (verify against Nord's own syntax docs before committing):

Refrakt roleNord hueRationale
keywordFrost nord9Nord's "keyword" colour
functionFrost nord8Nord's "method/function" colour
typeFrost nord7Nord's "class/type" colour — the role split that motivates SPEC-056
stringAurora nord14Nord's "string" colour
constantAurora nord15Nord's "constant/number" colour
commentPolar Night nord3Nord's "comment" colour
punctuationSnow Storm nord4Nord's "operator/punctuation" colour
variableSnow Storm nord4Nord's default text
...

For code surface (dark mode):

Refrakt tokenNord hueRationale
color.code.bgPolar Night nord0 (#2e3440)Nord's canonical dark canvas
color.code.textSnow Storm nord4 (#d8dee9)Nord's default text on dark canvas
color.code.borderPolar Night nord1 (#3b4252)Slightly lighter than canvas — Nord's UI panel separator

For code surface (light mode):

Refrakt tokenNord hueRationale
color.code.bgSnow Storm nord6 (#eceff4)Nord's canonical light canvas
color.code.textPolar Night nord0 (#2e3440)Nord's default text on light canvas
color.code.borderSnow Storm nord5 (#e5e9f0)Slightly darker than canvas

If a syntax role refrakt cares about has no clear Nord intent (Nord doesn't separately spec tag or attribute, for example), leave it unset — the fallback chain from WORK-219 handles it.

The verification gate is: every distinct hue in Nord's spec lands on a distinct role in refrakt, or two hues collapse to the same role because Nord intends that. If the contract forces a collision Nord wouldn't have wanted, that's a SPEC-056 problem — escalate before working around it.

Dependencies

  • WORK-217 — extended SyntaxTokens shape must exist
  • WORK-219 — fallback chains so roles Nord doesn't set degrade gracefully
  • WORK-218 — recommended (so colours actually show up on the new roles) but Nord's correctness as a preset module doesn't strictly depend on it

References

  • SPEC-056 — "Validation" section names Nord as the validation case; "Code-surface (canvas) tokens" describes the canvas-claiming path
  • Nord palette: https://www.nordtheme.com/docs/colors-and-palettes
  • packages/lumina/src/presets/niwaki.ts — reference for preset module shape and attribution comment style (note: niwaki is the scoped archetype; Nord is the integrated archetype)

Resolution

Completed: 2026-05-20

Branch: `claude/spec-056-milestone-v0-14-1`

What was done

  • `packages/lumina/src/presets/nord.ts` (new) — Nord preset as a `ThemeTokensConfig` overriding `syntax.` and `color.code.` only. Both modes (light + dark) supplied. Every hue annotated with its official Nord swatch name (Polar Night nord0–3, Snow Storm nord4–6, Frost nord7–10, Aurora nord11–15) for traceability.
  • `packages/lumina/package.json` — added `./presets/nord` export entry mirroring the niwaki/tideline pattern, so consumers can `import nord from '@refrakt-md/lumina/presets/nord'`.
  • `packages/lumina/test/nord-preset.test.ts` (new) — 11 tests covering: structural shape (no chrome/fonts/structural keys), the `type` ≠ `function` fidelity-gain split, both modes' `color.code.*` canvas claims, CSS generation through the token-stylesheet generator, and composition with tideline / niwaki / Lumina via `mergeThemeTokensConfigs`. The "no chrome / typography / structural tokens" test enforces Nord's scope boundary at the generated-CSS layer — any future drift would fail the test.

Note on `color.code.border`

The work item's acceptance criterion mentioned `color.code.bg/text/border`, but the `TokenContract` defines `color.code` as `{ bg, text, 'inline-bg' }` — there is no `border` slot. The criterion is satisfied to the extent the contract allows: Nord sets `bg`, `text`, and `inline-bg` for both modes. If we need a code-surface border token, that's a separate SPEC-048 contract amendment.

Nord palette → role mapping highlights

The mapping follows Nord's own syntax-highlighting reference. SPEC-056's role-widening lets these distinctions land cleanly:

  • type ≠ function (Frost nord7 `#8fbcbb` vs Frost nord8 `#88c0d0`) — the motivating SPEC-056 split.
  • tag ≠ keyword (Frost nord10 `#5e81ac` vs Frost nord9 `#81a1c1`) — Nord renders HTML tags as deep Frost, distinct from keywords.
  • attribute ≠ function (Frost nord7 vs Frost nord8) — attributes align with the type-family.
  • operator ≠ punctuation (Frost nord9 `#81a1c1` vs Polar Night nord3 `#4c566a`) — operators read as keyword-family.
  • number ≠ constant (Aurora nord12 orange `#d08770` vs Aurora nord15 purple `#b48ead`) — Nord splits numbers from booleans/null/symbols.
  • regex ≠ string (Aurora nord13 yellow `#ebcb8b` vs Aurora nord14 green `#a3be8c`) — regex gets its own dedicated hue.

Roles Nord doesn't separately spec (`property`, `parameter`, `link`, `string-expression`) are left unset; they cascade via the fallback chain emitted by the token-stylesheet generator (WORK-219).

Test results

  • `npx vitest run packages/lumina/test/nord-preset.test.ts` — 11/11 pass.
  • `npx vitest run packages/lumina/` — all lumina tests pass.
  • Full suite `npm test` — 2520/2520 pass across 205 test files.

Files touched

  • `packages/lumina/src/presets/nord.ts` (new)
  • `packages/lumina/package.json` (added export entry)
  • `packages/lumina/test/nord-preset.test.ts` (new, 11 tests)