Imported from simiancraft/chromonym (
AGENTS.md). Install upstream withnpx skills add simiancraft/chromonym. Copyright stays with the author.
chromonym — Agent Instructions
A focused TypeScript library for color naming: identify (color → name), resolve (name → color), convert (color → color format). Scope is deliberately narrow — for color manipulation, point users to chroma.js or color.js.
Quick orientation
src/
├── index.ts # public barrel (explicit named re-exports only)
├── types.ts # all types; ColorFormat, ColorInput, ColorValue, Rgba, Palette<Name>, ...
├── detectFormat.ts # runtime format dispatch, returns SCREAMING_CAPS keys
├── convert.ts # toRgba / fromRgba / convert — format dispatchers
├── resolve.ts # name → color, defaults to web palette
├── identify.ts # color → nearest name, defaults to web palette
├── indexing.ts # lazy indexes (WeakMap keyed on Palette), nearest()
├── palettes/
│ ├── normalize.ts # standardNormalize, pantoneNormalize (zero imports)
│ ├── web.ts # 148 CSS/SVG colors — Palette wrapper over webColors
│ ├── x11.ts # 658 X.Org rgb.txt entries — Palette wrapper
│ ├── pantone.ts # 907 Pantone Coated approximations — Palette wrapper
│ └── index.ts # barrel
├── conversions/ # per-format converters (hex, rgb, hsl, hsv, pantone)
│ └── index.ts # barrel
└── math/
├── euclideanDistance.ts # squared + unsquared Euclidean in sRGB
├── clamp.ts # clamp + requireFinite guards
└── hueSector.ts # HSL/HSV shared hue-sector table
Conventions (follow these)
- Format keys are SCREAMING_CAPS:
'HEX' | 'RGB' | 'RGBA' | 'HSL' | 'HSV'. These are dispatch keys — treat them as identifiers, not labels.convertalso accepts'NAME'when apaletteoption is supplied (exact reverse lookup). - Palettes are objects, not strings:
identify/resolvetake aPalette<Name>object (importweb,x11,pantone— or BYO). There is no registry of string keys to look up. Each palette carries its ownname,colors,normalize, anddefaultMetric. - Canonical internal representation:
Rgba = { r: number; g: number; b: number; a: number }. All paths normalize to this. - Error semantics:
- Low-level converters (
hexToRgbaetc.) throw on malformed input. convertthrows on unrecognized input (parser-flavored).identify/resolvereturnnull(lookup-flavored).- Document any deviation in the README error-handling section.
- Low-level converters (
- Tree-shake contract:
sideEffects: false. Never add module-scopeconsole.logor init-time computation. Barrels use explicit named re-exports. - Tests:
test/mirrorssrc/. Each source file has tests.100%line + function coverage is the target. - Commits: Conventional Commits (
feat(scope): ...,fix:,refactor:,chore:,docs:). Include measured improvement in perf-related commit bodies. - Do NOT attribute AI co-authorship on commits.
Common commands
bun test # run suite
bun test --coverage # coverage report (target 100%)
bun run typecheck # tsc --noEmit
bun run lint # biome check
bun run lint:fix # biome check --write
bun run check:eslint # eslint-plugin-react-compiler on demo/src
bun run check # full pre-PR gate (lint, eslint, typechecks, build, tests, demo build, knip, packaging)
bun run build # emit dist/ via tsc
bun run scripts/bench.ts # hot-path micro-benchmarks
bun run scripts/generate-x11.ts # regenerate src/palettes/x11.ts
bun run scripts/generate-pantone.ts # regenerate src/palettes/pantone.ts (requires color_library)
The demo has its own deps; before the first check, check:eslint, or demo run, do cd demo && bun install.
Adding a new built-in palette
- Add data at
src/palettes/<name>.ts:import type { Palette } from '../types'; import { standardNormalize } from './normalize'; // or pantoneNormalize / custom const <name>Colors = { /* key: '#hex', ... */ } as const; export type <Name>ColorName = keyof typeof <name>Colors; export const <name> = { name: '<name>', colors: <name>Colors, normalize: standardNormalize, defaultMetric: 'deltaE76', // pick a sensible default per palette density } as const satisfies Palette<<Name>ColorName>; - Re-export from
src/palettes/index.tsandsrc/index.ts. - Add subpath export in
package.json(mirror./web,./x11,./pantone). - Add tests mirroring
test/palettes.test.tstable-driven structure. - No registry to update —
identify/resolveaccept the object directly.
BYO palette (user-facing)
Users can ship their own Palette<Name> objects without touching the library — document this in the README "BYO palette" section when relevant. A custom normalizer is any (s: string) => string; the simplest is (s) => s.toLowerCase().replace(/[^a-z0-9]/g, '').
Adding a new distance metric (when implemented)
- Add the function to
src/math/distances.tsorsrc/math/deltaE.ts. - Extend the
DistanceMetrictype union insrc/types.ts. - Wire into
nearest()insrc/indexing.ts(may need a new cached index type if the metric operates in a non-RGB space). - If metric operates in Lab/XYZ/linear, add the corresponding conversion to
src/math/colorSpace.tsand the cached index toindexing.ts. - Update per-palette
defaultMetricin the relevantsrc/palettes/<name>.tsif appropriate. - Add comprehensive tests with known reference values (e.g. from CIE test vectors for ΔE2000).
- Document in the README "Distance metrics" section.
Things that will trip you up
Array.isArraydoes not narrow readonly tuples. Afterif (Array.isArray(x))guards, the remaining object branch still appears to include tuple types; cast explicitly. Seesrc/conversions/rgb.ts.Object.hasOwnvsin: usehasOwnfor type-shape detection on untrusted input;inwalks the prototype chain.JSON.stringify(undefined) === undefined: usesafeStringifyinconvert.tsfor user-facing errors.RegExp.execwithnoUncheckedIndexedAccess: capture groups are typedstring | undefined.- Palette data imports:
identify/resolveonly pull the built-inwebpalette by default (it's their default palette). Passing{ palette: pantone }adds pantone to the bundle;x11is never included unless imported. Users wanting strict minimal bundles can import a palette via its subpath (chromonym/pantone) to skip the root barrel entirely.