Problem
Every sibling surface pulses when the mic is hot (grabber bar, collapsed pill, kiosk composer, HomePill) — but the overlay's own mic button, the one users press on the primary surface, only turns accent-colored and never pulses. Color and motion disagree. There is also a minor optical inconsistency: lucide icons render at 26px while hand-drawn glyphs render at 30px, so the '+' glyph looks larger than the mic/send icons.
Current state (file refs)
SoftButton (packages/ui/src/components/shell/ContinuousChatOverlay.tsx:309-369): borderless 44×44 hit target (WCAG 2.5.5), lucide icons at h-[26px] w-[26px] (:363), hand-drawn glyphs at h-[30px] w-[30px] (:365). active renders text-accent only — no pulse (:358).
- Overlay mic button call site:
ContinuousChatOverlay.tsx:4400-4427 — active={recording || handsFree || transcriptionMode} (:4420), static accent, NO pulse.
- Sibling surfaces that DO pulse: grabber bar
animate-pulse bg-accent (:509-513, glow :3703); collapsed pill same pulse via glow={listening || responding} (:582-586, :4445); kiosk ChatSurface mic active && "animate-pulse" (glass-composer.tsx:108); HomePill animate-pulse bg-warn/70 on listening (HomePill.tsx:66-68).
- Reduced-motion pattern already in the file:
motion-reduce:animate-none at :512.
Proposed change
- Pulse the overlay mic button on the SAME predicate that already sets
active — recording || handsFree || transcriptionMode (:4420) — so color and motion never disagree. Reuse the existing animate-pulse + motion-reduce:animate-none pattern (:512); orange accent only, no background/border change, no blue.
- Normalize composer icon optics: bring the lucide 26px and glyph 30px sizing to a single optically-balanced size so the '+', mic, and send read at the same visual weight. Keep the 44×44 hit target intact (do NOT shrink the hit area, only the visible glyph metrics).
Acceptance criteria
Evidence required (post INLINE in the PR)
- MP4 walkthrough: start listening → mic button pulses; stop → pulse stops; with OS reduced-motion on → no pulse. Desktop AND mobile.
- JPG before (mic static while recording — the wrong behavior) + after (mic pulsing) screenshots, plus a side-by-side of the '+'/mic/send optical sizing before/after.
- The new regression-test output (pass).
- Frontend console logs from the voice session showing the recording state transitions.
Workstream: Chat window UX · design doc · MVP board
Problem
Every sibling surface pulses when the mic is hot (grabber bar, collapsed pill, kiosk composer, HomePill) — but the overlay's own mic button, the one users press on the primary surface, only turns accent-colored and never pulses. Color and motion disagree. There is also a minor optical inconsistency: lucide icons render at 26px while hand-drawn glyphs render at 30px, so the '+' glyph looks larger than the mic/send icons.
Current state (file refs)
SoftButton(packages/ui/src/components/shell/ContinuousChatOverlay.tsx:309-369): borderless 44×44 hit target (WCAG 2.5.5), lucide icons ath-[26px] w-[26px](:363), hand-drawn glyphs ath-[30px] w-[30px](:365).activerenderstext-accentonly — no pulse (:358).ContinuousChatOverlay.tsx:4400-4427—active={recording || handsFree || transcriptionMode}(:4420), static accent, NO pulse.animate-pulse bg-accent(:509-513, glow:3703); collapsed pill same pulse viaglow={listening || responding}(:582-586,:4445); kioskChatSurfacemicactive && "animate-pulse"(glass-composer.tsx:108);HomePillanimate-pulse bg-warn/70on listening (HomePill.tsx:66-68).motion-reduce:animate-noneat:512.Proposed change
active—recording || handsFree || transcriptionMode(:4420) — so color and motion never disagree. Reuse the existinganimate-pulse+motion-reduce:animate-nonepattern (:512); orange accent only, no background/border change, no blue.Acceptance criteria
recording || handsFree || transcriptionModeis true, and stops when it clears — matching the grabber/pill.motion-reduce:animate-noneis respected: with reduced motion the button stays statically accent, no pulse.ContinuousChatOverlay.test.tsx(or e2e) case asserts the mic button carries the pulse class exactly when the recording predicate is true and drops it otherwise.Evidence required (post INLINE in the PR)
Workstream: Chat window UX · design doc · MVP board