Initial Commit
This commit is contained in:
23
openspec/archive/2026-05-22-2026-05-20-colours/proposal.md
Normal file
23
openspec/archive/2026-05-22-2026-05-20-colours/proposal.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Proposal: colours
|
||||
|
||||
**Change ID**: `2026-05-20-colours`
|
||||
**Package**: `@suaveplan/colours`
|
||||
**Status**: PROPOSED
|
||||
**Author**: Kimi Code CLI
|
||||
|
||||
- **Design**: ./design.md
|
||||
- **Spec delta**: ./specs/colours/spec.md
|
||||
|
||||
## Summary
|
||||
|
||||
Introduce `@suaveplan/colours`, a universal-tier colour manipulation library with parsing, conversion, analysis, mixing, blending, and palette generation. Zero runtime dependencies beyond `@suaveplan/error`, `@suaveplan/types`, and `tslib`.
|
||||
|
||||
## Impact
|
||||
|
||||
### Affected dependents
|
||||
|
||||
None — this is a new leaf package. Future consumers may include `@suaveplan/image`, `@suaveplan/ui`, and theming systems.
|
||||
|
||||
## Parity Checklist
|
||||
|
||||
This is a genesis-original package; no upstream source exists.
|
||||
@@ -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 -->
|
||||
20
openspec/archive/2026-05-22-2026-05-20-colours/tasks.md
Normal file
20
openspec/archive/2026-05-22-2026-05-20-colours/tasks.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Tasks: colours
|
||||
|
||||
1. Create OpenSpec change folder with proposal, tasks, spec delta
|
||||
2. Scaffold package: package.json, tsconfig, vitest config, vite config, README
|
||||
3. Implement error-codes (`COLOUR_CODES` via `defineCodes`)
|
||||
4. Implement errors (`ColourError extends SuaveplanError` with typed factories)
|
||||
5. Implement colour-types (Zod schemas for RGBA, HSLA, LAB, LCH, OKLAB, OKLCH, CMYK, ColourInput)
|
||||
6. Implement utils (clamp, roundChannel, roundPercent, roundAlpha, isFiniteNumber)
|
||||
7. Implement parser (hex, rgb/rgba, hsl/hsla, lab, lch, oklab, oklch, named colour parsing)
|
||||
8. Implement conversions (rgb↔hsl, rgb↔hsv, rgb↔xyz, xyz↔lab, lab↔lch, rgb↔oklab, oklab↔oklch, rgb↔cmyk)
|
||||
9. Implement Colour class (chainable manipulation, analysis, output formatters)
|
||||
10. Implement colour-factories (random, fromTemperature, blend, steps, palette)
|
||||
11. Write tests for all modules (100% istanbul coverage target)
|
||||
12. Write co-located .md docs for every feature module
|
||||
13. Run lint, typecheck, test, build, coverage gates
|
||||
14. Plugin: `@suaveplan/colour-convert-hsluv` — HSLuv/HPLuv conversions and harmonies
|
||||
15. Plugin: `@suaveplan/colour-convert-cieluv` — CIELUV, xyY, LMS, Bradford adaptation
|
||||
16. Plugin: `@suaveplan/colour-filter-cmc` — CMC l:c Delta-E distance
|
||||
17. Plugin: `@suaveplan/colour-filter-cct` — Correlated Colour Temperature
|
||||
18. Fold spec delta into `openspec/specs/colours/spec.md` and archive
|
||||
Reference in New Issue
Block a user