# Capability Spec: colours **Package**: `@suaveplan/colours` **Status**: STABLE > Every requirement below carries a `**Verified by:**` line at authoring time. ## ADDED Requirements ### Requirement: Stable error code registry via defineCodes The package SHALL declare a single `defineCodes("COLOUR", [...])` registry named `COLOUR_CODES`. No hand-typed string literals for codes SHALL appear in `src/` or tests. #### Scenario: Code values use COLOUR.* namespace - **WHEN** reading `COLOUR_CODES.INVALID_INPUT` - **THEN** the value is exactly `"COLOUR.INVALID_INPUT"` **Verified by:** `src/error-codes/error-codes.test.ts::"code values use COLOUR.* namespace"` --- ### Requirement: Typed error hierarchy extending SuaveplanError The package SHALL export `ColourError` extending `SuaveplanError` with typed factory functions for every failure mode. #### Scenario: Factory functions produce correctly-coded errors - **WHEN** calling `colourInvalid("bad hex")` - **THEN** the result has code `COLOUR.INVALID_INPUT` and message context **Verified by:** `src/errors/errors.test.ts::"factory functions produce correctly-coded errors"` --- ### Requirement: Colour parsing from multiple formats The package SHALL parse colour input from hex (3/4/6/8 digit), modern CSS `rgb()`, `hsl()`, `lab()`, `lch()`, `oklab()`, `oklch()`, named colours, and plain RGB/RGBA/HSL/HSLA/HSV/LAB/LCH/OKLAB/OKLCH objects. #### Scenario: Parse 6-digit hex - **WHEN** parsing `"#ff9900"` - **THEN** the result is `{ r: 255, g: 153, b: 0, a: 1 }` #### Scenario: Parse modern space-separated rgb with alpha - **WHEN** parsing `"rgb(255 128 0 / 0.5)"` - **THEN** the result alpha is `0.5` #### Scenario: Parse named colour - **WHEN** parsing `"transparent"` - **THEN** the result is `{ r: 0, g: 0, b: 0, a: 0 }` #### Scenario: Reject invalid input - **WHEN** parsing `"not-a-colour"` - **THEN** it throws `ColourError` with code `COLOUR.INVALID_INPUT` **Verified by:** `src/parser/parser.test.ts::"parses hex"` AND `src/parser/parser.test.ts::"parses modern rgb with alpha"` AND `src/parser/parser.test.ts::"parses named colours"` AND `src/parser/parser.test.ts::"rejects invalid input"` --- ### Requirement: Colour space conversions The package SHALL provide bidirectional conversions between RGB, HSL, HSV, XYZ, LAB, LCH, OKLAB, OKLCH, and CMYK. #### Scenario: RGB to HSL round-trip - **WHEN** converting `{ r: 255, g: 128, b: 0 }` to HSL and back - **THEN** the result is approximately the original RGB values #### Scenario: LAB to LCH preserves chroma and hue - **WHEN** converting `{ l: 50, a: 20, b: -30 }` to LCH - **THEN** `c` and `h` are positive finite numbers **Verified by:** `src/conversions/conversions.test.ts::"rgb to hsl round-trip"` AND `src/conversions/conversions.test.ts::"lab to lch preserves chroma and hue"` --- ### Requirement: Chainable Colour class manipulation The package SHALL provide a `Colour` class with immutable chainable methods: `lighten`, `darken`, `saturate`, `desaturate`, `rotate`, `alpha`, `fade`, `opaquer`, `negate`, `grayscale`, and `mix`. #### Scenario: Lighten and darken are inverse - **WHEN** `colour.lighten(20).darken(20)` - **THEN** the result approximately equals the original #### Scenario: Mix in oklab space - **WHEN** mixing red and blue with weight 50 in oklab - **THEN** the result is a perceptually uniform mid-tone **Verified by:** `src/colour/colour.test.ts::"lighten and darken are inverse"` AND `src/colour/colour.test.ts::"mix in oklab space"` --- ### Requirement: Colour analysis and comparison The package SHALL compute luminance (WCAG), perceived brightness, contrast ratio, isDark/isLight, bestContrast selection, and distance in euclidean/CIE76/CIE94/oklab spaces. #### Scenario: Contrast ratio between black and white - **WHEN** computing contrast between black and white - **THEN** the result is `21` #### Scenario: bestContrast selects highest ratio - **WHEN** calling `bestContrast` with black and white against red - **THEN** the result is the colour with the highest contrast ratio **Verified by:** `src/colour/colour.test.ts::"contrast ratio between black and white"` AND `src/colour/colour.test.ts::"bestContrast selects highest ratio"` --- ### Requirement: Output formatters The package SHALL format colours to hex (3/6/8), rgb, rgba, hsl, hsla, hsv, lab, lch, oklab, oklch, cmyk, and named. #### Scenario: Hex round-trip - **WHEN** formatting a parsed hex colour back to hex - **THEN** the result matches the canonical 6-digit form **Verified by:** `src/colour/colour.test.ts::"hex round-trip"` --- ### Requirement: Colour factories The package SHALL provide `randomColour`, `colourFromTemperature`, `blendColours`, `colourSteps`, and `generatePalette`. #### Scenario: Steps produces correct count - **WHEN** calling `colourSteps('#ff0000', '#0000ff', 5)` - **THEN** the result has exactly 5 colours **Verified by:** `src/colour-factories/colour-factories.test.ts::"steps produces correct count"` --- ### Requirement: Zod schema validation on public boundaries The package SHALL validate `ColourInput` via Zod schemas at public entrypoints. Invalid input SHALL throw `COLOUR_CODES.INVALID_INPUT`. #### Scenario: Zod rejects non-finite channel values - **WHEN** constructing `new Colour({ r: NaN, g: 0, b: 0 })` - **THEN** it throws `ColourError` with code `COLOUR.INVALID_INPUT` **Verified by:** `src/parser/parser.test.ts::"rejects NaN channel values"` --- ### Requirement: Subpath exports and sideEffects false The package SHALL declare `sideEffects: false` and provide subpath exports for `./types/colour-types`, `./errors/errors`, `./error-codes/error-codes`, `./parser/parser`, `./conversions/conversions`, `./colour/colour`, and `./colour-factories/colour-factories`. #### Scenario: Tree-shake test passes - **WHEN** bundling with only `parseColour` imported - **THEN** `Colour` class code is not included in the output **Verified by:** `tests/tree-shake.test.ts::"tree-shakes unused exports"`