PR #132 (color themes) only tokenized the high-visibility chrome.
Inbox / contacts / pipelines / broadcasts / automations / auth /
settings sub-forms still had ~150 hard-coded `violet-*` classes
across ~32 files, so picking a non-violet theme felt half-broken —
sidebar and header changed colour, but message bubbles, deal cards,
and the broadcast wizard all stayed violet.
This is the mechanical follow-up: replace every `violet-N(/N)?`
with `primary(/N)?` so the theme picker themes the whole app.
Two non-trivial swaps inside the pass:
- `text-white` on a `bg-primary` button → `text-primary-foreground`.
Emerald and Amber define a *dark* `--primary-foreground` because
their primaries are bright; `text-white` over those would tank
contrast.
- `bg-primary hover:bg-primary` (hover identical to base — original
code had this latent bug from `hover:bg-violet-500`) → adds `/90`
on hover so the lift is visible.
Test update:
- `broadcast-status.test.ts` asserted shade numbers (`bg-X-N/10`).
Relaxed the regex to also accept token form (`bg-X/10`) since
brand-accent status badges now ride the active theme.
Files touched: 49. Lint clean (existing warnings only), 173/173
tests, typecheck + build green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the first test layer to the repo. Vitest is wired into the
existing CI workflow between typecheck and build so every PR gets the
same gating before merge. No source code under test was modified; this
PR only adds the test framework + tests.
Coverage focuses on the highest-impact pure modules — security-critical
crypto/HMAC, the phone-number pipeline every outbound message touches,
the rate-limiter that protects send/broadcast, automation activation
validation, and dashboard date math. 89 tests across 7 files; full run
is ~300ms.
Modules covered:
- lib/whatsapp/encryption — GCM round-trip, tamper detection (auth tag,
ciphertext, IV length, auth-tag length), legacy CBC compat, format
detection, malformed input
- lib/whatsapp/webhook-signature — valid/invalid HMAC, missing header,
wrong prefix, length-mismatch guard, fail-closed when secret unset
- lib/whatsapp/phone-utils — sanitize, normalize, phonesMatch with
trunk-prefix tail fallback, isValidE164 boundaries (7–15 digits,
non-zero start), phoneVariants insert/remove with dedupe,
isRecipientNotAllowedError detection
- lib/rate-limit — first-request math, exact-limit boundary, per-key
isolation, window expiry with fake timers, 429 response shape,
Retry-After clamp
- lib/automations/validate — every step type's required fields, wait
amount/unit boundaries, webhook URL validation (missing / non-http /
malformed), assign_conversation mode branching, recursive condition
walking with stable dot-paths, unknown step types
- lib/broadcast-status — known status lookups, pulse flag, dark-theme
class shape, fallback for unknown strings
- lib/dashboard/date-utils — startOfLocalDay, daysAgoStart, localDayKey
zero-padding, lastNDayKeys (1, N, month crossing), Monday-first DOW
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>