Initial Commit
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
<!-- markdownlint-disable MD024 -->
|
||||
|
||||
# Capability Spec Delta: colours (2026-05-20-colours)
|
||||
|
||||
**Package**: `@suaveplan/colours`
|
||||
**Status**: PROPOSED
|
||||
|
||||
> 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<ColourCode>` 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"`
|
||||
|
||||
<!-- markdownlint-enable MD024 -->
|
||||
Reference in New Issue
Block a user