Build History

How this actually got built.

Eight rounds so far. This is the condensed version — the expanded, blow-by-blow write-up of each real bug (what broke, how it was found, how it was fixed, and how the fix was verified) lives in the repo's iterations/ directory and HANDOFF.md.

Round 1 — local control from zero

Started with no credentials at all for a Bytech A19 Wi-Fi RGB+CCT bulb. A mitmproxy interception attempt failed (the Tuya app's real API traffic runs over HTTP/3/QUIC, which slips past a standard proxy). The Tuya IoT Platform's QR-link step scanned but never confirmed — a known data-center-mismatch bug. What actually worked: tuya-device-sharing-sdk, the same cloud-assisted QR-login flow Home Assistant's own Tuya integration uses, needing only the app's User Code — no developer platform project at all. Verified with real color changes: red, then blue, then yellow, watched on the physical bulb.

Round 2 & 3 — audio-reactive lighting, discovery, orchestration, security

Built the 12-mode audio engine, network auto-discovery, multi-bulb orchestration, and the PIN gate. Five real bugs found via actual testing, not code review alone:

All five are documented with the actual failure evidence and the fix verification in iterations/001 through 004.

Round 4 — parallel-phase build, mobile fix, Tailscale, docs (v0.3.0)

Four roadmap phases built in parallel via isolated subagent worktrees, hub-verified and hand-merged: two new audio modes, PIN-gate hardening (session revocation, audit logging, per-IP rate limiting), a stdlib CLI (bulbctl), and a real 76-test pytest suite. Followed by a QoL round (live-ticking timers/status, visual polish, power-user niceties) and a mobile-friendliness fix shipped as its own PR. Three real bugs found via actual testing:

Tailscale Serve was set up and verified end-to-end as the actual off-LAN access path (tailnet-only HTTPS, PIN gate enabled alongside it) — 21/21 live security checks passed.

Round 5 — Week 1 roadmap (open as PR #68, not yet merged)

Four roadmap phases built in parallel via isolated subagent worktrees, hub-verified through real hand-resolved git merges (not patch application, since all four phases touched the same core classes) across roughly 40 conflict blocks: new audio modes + a tempo tracker (Phase A), signal conditioning + a golden-value test harness (Phase B), multi-bulb orchestration + zones (Phase C), and session management + safety/accessibility (Phase D). This is currently open as PR #68, tracked in issues #64–#67, not yet merged to master — pending Owen's own testing.

5 real post-merge test failures were found and fixed (a dropped mode-validation check, a rate-limiter state leak across tests, stale test mocks, and a route-vs-module naming collision). Live-testing this over Tailscale then surfaced 3 more real bugs, this time via a physical bulb that had gone unreachable on the network:

All verified with real timing tests and Playwright screenshots against the actual unreachable device — full backend suite stayed green (353/353) throughout.

Rounds 6 & 7 — Week 2 hardening, then CVEs, live streaming and the docs browser

Week 2 shipped in four phases and closed: PIN-gate hardening (configurable lockout with exponential backoff, enforced PIN complexity, separately-revocable guest PINs, IPv6-correct per-IP tracking) and a general per-IP API rate limiter; TLS and reverse proxy (validated Caddy and nginx configs, systemd units, trusted-proxy X-Forwarded-For handling, a /healthz probe); audit logging, secrets management and backup/restore; then observability and network resilience.

Round 7 closed both applicable starlette CVEs (FastAPI 0.115.6 → 0.141.1), made audio analysis 57% faster with bit-identical output, moved beat detection onto the bass band rather than broadband RMS — measured 9/9 tempos correct against 1/9 on a dense mix — added a live SSE stream, expanded the genre presets from 8 to 24, and built the in-app documentation browser. Six real bugs, including a local_key leak through a tinytuya exception string, and auth-state file corruption that bricked the entire dashboard because a non-atomic write left half a file the gate read on every request.

Round 8 — audio-reactive lighting actually works

The flagship feature had never once worked in the way the dashboard is actually deployed. A Linux container on Docker Desktop for Windows has no access to host audio devices, so a session there ran, reported itself running, and never reacted to a sound. This round closed that end to end, and for the first time the bulb reacted to real audio through the deployed container.

Latency stopped being a claim and became a number: per-stage capture, analysis and bulb round-trip, each with a typical figure and a worst case, live in the dashboard. Then analysis was rebuilt to run a short hop over a longer window, so latency and frequency resolution stopped fighting each other. Software sound-to-decision went from 12.19ms to 6.44ms, inside the 10ms target — proven by the instrumentation rather than asserted. analyze_frame() itself was not modified; all 21 golden-value tests passed with no expected values changed, which is what proves the change was timing and nothing else.

The interesting failures were the ones only real hardware exposed:

Also shipped: a one-command switch between container and native-host audio, minimum dwell adjustable while a session runs (so tuning by ear doesn't mean stopping and restarting), and a bridge protocol revision that lets the capture device be chosen in the dashboard — necessary because the container cannot enumerate the host's audio devices, so the tool now reports its own inventory over the same socket.

729 tests passing, up from 671 at the start of the round.

Known-fragile / watch for

What's not built (intentionally)

Full narrative with every command run and its actual output: the complete HANDOFF.md.