feat(canvas): 4096^2 canvas, 256-color palette (u8 byte-aligned), custom picker (#5)

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)
This commit is contained in:
tiennm99 authored and GitHub committed 2026-04-18 13:47:01 +07:00
1 parent 0bf51a410e
commit cfbac2a586
12 files changed
+371 -214

No files matched your search

+2 -1
View File
@@ -69,6 +69,7 @@ For hobby traffic (< few hundred users/day), free tiers are sufficient. Upstash
## Troubleshooting
- **Canvas loads empty**: Check Upstash credentials in secrets
- **Pixels don't persist**: Verify BITFIELD support — test with `redis-cli BITFIELD canvas SET u5 #0 1`
- **Pixels don't persist**: Verify BITFIELD support — test with `redis-cli BITFIELD rplace:canvas:v2 SET u8 #0 42`
- **Old 32-color canvas still visible after deploy**: the canvas key is versioned (`rplace:canvas:v2`). The old `rplace:canvas` key is orphaned — run `redis-cli DEL rplace:canvas` once to reclaim memory.
- **WebSocket not connecting**: Ensure Durable Object migration ran (check `wrangler.json` migrations)
- **Rate limiting not working**: Verify `SET key value NX EX 1` returns `"OK"` / `null` as expected on your Upstash tier