AGENTS.md

July 20, 2026 · View on GitHub

Package Overview

  • Package: @tenphi/glaze
  • Repository: https://github.com/tenphi/glaze
  • Description: Zero-dependency color theme generator built on OKHST (OKHSL with a contrast-uniform tone axis), with WCAG + APCA contrast solving for light, dark, and high-contrast schemes.
  • Runtime: Node >= 20, browsers, edge. Pure math, no I/O.
  • Output formats: ESM + CJS, with full .d.ts.

Project Structure

glaze/
├── src/                       Source
├── docs/                      Long-form docs (okhst / api / methodology / migration)
├── assets/                    Logo + brand assets
├── scripts/                   Repo helpers
├── README.md                  Compact intro
├── AGENTS.md                  This file
├── CHANGELOG.md               Auto-generated by Changesets
├── package.json
├── tsconfig.json
├── tsdown.config.ts           Build config
├── vitest.config.ts           Test config
└── eslint.config.js

src/ files

FileDescription
src/index.tsPublic barrel; re-exports glaze, every public type, the contrast-solver utilities, and the OKHSL/sRGB/OKLab math helpers.
src/glaze.tsglaze() factory + attached statics: palette, paletteFrom, color, colorFrom, themeFrom, from (alias), fromHex, fromRgb, shadow, format, configure, getConfig, resetConfig, isThemeExport / isColorTokenExport / isPaletteExport. Thin wiring layer over the focused modules below.
src/theme.tsSingle-theme factory (createTheme). Owns the mutable ColorMap, the resolve() cache (versioned against getConfigVersion()), and the tokens / tasty / json / css / extend / export methods. export(override?) deep-clones defs and freezes effective config with kind/version.
src/palette.tsMulti-theme composition (createPalette / createPaletteFromExport). Shared per-theme driver buildPaletteOutput handles prefix resolution, primary duplication, collision filtering. Authoring: export(override?), theme / themes / list / primary.
src/color-token.tsStandalone glaze.color() tokens. Owns the value-shorthand parser (hex 3/6/8, rgb() / hsl() / okhsl() / oklch(), { r, g, b }, { h, s, l }, { l, c, h }), the structured-input validator, the two factory paths, sparse local config + live resolve, and the JSON-safe export / glaze.colorFrom rehydrate round-trip.
src/serialize.tsAuthoring-export helpers: GLAZE_EXPORT_VERSION, assertExportKind / assertExportVersion, and isThemeExport / isColorTokenExport / isPaletteExport type guards.
src/resolver.tsFour-pass solver (light → light-HC → dark → dark-HC). Stores canonical tone (t) in variants; per-scheme branches for regular, shadow, and mix defs; integrates the contrast solver, and the OKHST tone helpers. Converts to/from OKHSL lightness only at the mix/shadow edges. Pre-seeds externally-resolved bases for glaze.color({ base }).
src/okhst.tsThe OKHST tone layer. REF_EPS, tone↔lightness transfers (toTone/fromTone, toneFromY/yFromTone), OKHST↔OKHSL conversions, variantToOkhsl (tone→lightness at render), normalizeToneWindow ([lo,hi] / {lo,hi,eps} / false{lo,hi,eps}), mapToneForScheme (scheme inversion + window remap, HC bypass), and schemeToneRange for the solver. Replaces the old Möbius scheme-mapping.ts.
src/contrast-solver.tsTone-based binary-search solver for WCAG and APCA. Public API: findToneForContrast, findValueForMixContrast, resolveContrastForMode, resolveMinContrast, apcaContrast. Closed-form WCAG seed + tone search.
src/shadow.tsShadow + mix def predicates (isShadowDef, isMixDef), default ShadowTuning, tuning merge, the actual computeShadow math (hue blend, saturation cap, lightness clamp, tanh alpha curve) operating on OKHSL lightness at the edge, and circularLerp for hue.
src/hc-pair.tsTiny shared helpers: HC-pair selection (pairNormal / pairHC), tone-value parsing (parseToneValue for absolute / relative / 'max'/'min' extremes, isExtremeTone), root-tone discrimination (isAbsoluteTone, now incl. extremes), clamp, hue resolution (resolveEffectiveHue), relative-value parsing.
src/formatters.tsVariant→string dispatch (okhsl / rgb / hsl / oklch) and the four token-map shapes Glaze emits: buildTokenMap (Tasty #name keys + state aliases), buildFlatTokenMap (per-variant maps), buildJsonMap (per-color JSON), buildCssMap (CSS custom-property declarations). Also the resolveModes helper used everywhere.
src/okhsl-color-math.tsOKHSL ↔ linear-sRGB ↔ gamma-sRGB ↔ OKLab conversions, hex parsing (3/6/8 digits), gamut clamping, and the formatOkhsl / formatRgb / formatHsl / formatOklch writers. The only file with the actual color science.
src/config.tsGlobal config singleton. defaultConfig() is the one source of truth for defaults; configure() mutates the live object and bumps a monotonic configVersion so theme caches invalidate. snapshotConfig() powers glaze.getConfig(). freezeConfigForExport() freezes resolve-relevant fields for authoring export (pastel is instance-only).
src/validation.tsvalidateColorDefs (missing references, shadow-bg-cannot-be-shadow, mix-cannot-target-shadow, contrast-without-base, relative-without-base, etc.) + topoSort so the resolver processes each color after its dependencies.
src/warnings.tsDeduped contrast-unmet warnings. Caps cache at 256 entries to keep dev-server output bounded.
src/types.tsAll public TypeScript types: HCPair, MinContrast, ContrastSpec, RelativeValue, ExtremeValue ('max'/'min'), ToneValue, AdaptationMode, OkhstColor, ToneWindow ([lo,hi] | {lo,hi,eps} | false), ColorDef discriminated union (with tone + flip), GlazeConfig (with lightTone / darkTone / autoFlip), GlazeTheme, GlazePalette, GlazeColorToken, the *Export shapes, etc. ResolvedColorVariant stores { h, s, t, alpha }.
src/glaze.test.tsMain test suite — covers the factory surface, resolver behavior, palette composition, shadow/mix algorithms, and the standalone color token round-trip.
src/contrast-solver.test.tsTests for the binary-search solver in isolation.

docs/ files

FileDescription
docs/okhst.mdThe OKHST color model spec — tone math, REF_EPS, the T↔L invariant, the scheme pipeline, calibrated window defaults, the contrast metric, and the APCA verification step. Source of truth for the color model.
docs/api.mdFull API reference. Every method, every option, every type. Reads as a lookup, not a tutorial.
docs/methodology.mdPalette design methodology — how to build a palette from scratch. Covers naming conventions, surface ladders, the accent anchor pattern, contrast-driven disabled chips, shadows, and extend composition.
docs/migration.mdMigration & integration guide — choosing an export shape, wiring tokens into Tasty / CSS / JSON, prefix-map strategies, migrating from a legacy color system, and common pitfalls.

Commands

CommandDescription
pnpm buildBuild ESM + CJS via tsdown (with .d.ts and sourcemaps).
pnpm testRun all tests (Vitest 4).
pnpm test -- <pattern>Run a subset of tests.
pnpm test:watchTests in watch mode.
pnpm test:coverageTests with coverage report.
pnpm typecheckType-check without emitting.
pnpm lintESLint over src/.
pnpm lint:fixESLint with --fix.
pnpm formatPrettier write src/**/*.ts.
pnpm format:checkPrettier check (no writes).
pnpm changesetCreate a changeset entry.
pnpm releaseBuild and publish (used by CI).

Stack

  • Language: TypeScript (strict mode, consistent-type-imports enforced)
  • Build: tsdown — ESM + CJS, unbundled, dts + sourcemaps
  • Test: Vitest 4 with globals enabled
  • Lint: ESLint 10 + typescript-eslint + eslint-config-prettier
  • Format: Prettier — single quotes, semicolons, trailing commas, 80 cols
  • Versioning: Changesets
  • Package manager: pnpm 11

Code Conventions

  • TypeScript strict mode; consistent-type-imports enforced.
  • Test files use *.test.ts and live next to the module they test in src/.
  • Unused vars prefixed with _ are allowed.
  • No runtime dependencies — every bit of math is implemented from scratch.
  • Functional API pattern: glaze() factory with attached static methods; no classes.
  • Each module starts with a JSDoc block describing its responsibility — keep these in sync when behavior changes.

Adding a New Color Type

If you need to add a fourth ColorDef variant (alongside regular / shadow / mix), touch these files in order:

  1. src/types.ts — add the new interface and extend the ColorDef discriminated union.
  2. src/shadow.ts — add a discriminator predicate (isFooDef).
  3. src/validation.ts — extend validateColorDefs (missing-reference / cycle handling) and topoSort (record the new dependency edges).
  4. src/resolver.ts — add a per-scheme resolveFooForScheme branch and dispatch from resolveColorForScheme.
  5. src/formatters.ts — only if the output shape differs (it usually doesn't; the resolver returns a normal ResolvedColorVariant).
  6. src/glaze.test.ts — add coverage for the new def, including HC pairs, dark-mode behavior, and inheritance through extend.

For a new public method on glaze (e.g. another statics entry), the wiring lives entirely in src/glaze.ts; the implementation should still go in a focused module.

CI/CD & Release

  • CI: Runs lint, format check, typecheck, build, and tests on push to main and PRs.
  • Release: On push to main, changesets/action either creates a version PR or publishes to npm.
  • Snapshot: Comment /snapshot on a PR to publish a 0.0.0-snapshot.<sha> release.
  • npm trusted publishing: Uses OIDC provenance via the release GitHub environment; the workflow requires environment: release for npm to accept the OIDC token.
  • RELEASE_TOKEN secret: Fine-grained GitHub PAT (repo-scoped, contents: write + pull-requests: write) used by changesets/action for Git pushes and PR creation. Rotate before expiry.
  • Repo settings required: Settings → Actions → General → Workflow permissions → "Allow GitHub Actions to create and approve pull requests" must be enabled.

Before Pushing Changes

Follow .cursor/commands/submit-changes.md for the full release-oriented workflow. At minimum:

  1. Typecheckpnpm typecheck. Stop and fix errors before continuing.
  2. Lintpnpm lint. Stop and fix errors before continuing.
  3. Formatpnpm format so the diff matches Prettier output.
  4. Changeset — for any user-visible change (features, fixes, refactors, perf, docs that affect published behavior), add a markdown file in .changeset/ with a patch / minor / major bump and a concise user-facing summary. Skip only for purely internal changes (CI, repo churn, tests with no behavior change).
  5. Commit — Conventional Commits (feat, fix, refactor, test, docs, chore, perf, ci). Optional scope. Keep the subject short. Include the changeset file in the same commit.
  6. Push — never push to main. Confirm the current branch, then git push -u origin HEAD.