Canvas:
- CANVAS_W/H = 4096, total 16,777,216 pixels
- BITS_PER_PIXEL = 8 (byte-aligned) — raw Redis bytes are palette indices
- Canvas-decoder becomes an identity wrap/copy
- Storage BITFIELD uses u8; offset = y*W + x
- Redis key versioned to rplace:canvas:v2 so old 32-color/2048^2 data is
orphaned (operators can DEL the old key to reclaim memory)
Palette:
- 256 entries, generated deterministically:
- 0..15 = 16-step grayscale ramp (pure black -> pure white)
- 16..255 = 240 HSL wheel (4 lightness rings x 60 hues @ 82% saturation)
- nearestPaletteIndex(r,g,b) helper for custom-color snapping
UI:
- ColorPicker: 16-swatch favorites strip (grays + 8 accents) + current-color
swatch + expand toggle for the full 16x16 grid + "Custom..." button that
opens the native <input type="color"> and snaps to nearest palette entry
- Default selected color bumped to index 0 (black)
Tests + docs:
- canvas-decoder tests rewritten for identity u8 decode
- canvas-storage tests updated for u8 offsets
- image-to-palette tests anchored to PALETTE_BLACK=0 / PALETTE_WHITE=15 and
COLORS_RGBA[i] probes (no more hardcoded old 32-color indices)
- integration test uses u8 BITFIELD and canvas-aware bounds
- README, system-architecture, deployment-guide updated (storage math,
migration note for orphaned old key)