Closes vision-audit §4 "HUD button emitter has zero matches". The bus
protocol declared `BusInbound.frameAttachRequested` and the Swift
inbound handler at App/AppDelegate.swift:1696-1698 routed it into
`FrameAttachController.requestAttach(reason: .hudButton)`, but no React
component dispatched the message. Now the user has a clickable camera
affordance next to the chat input.
Changes:
- webview/packages/hud/src/chat/CameraButton.tsx (new): stateless button
that posts `{ type: 'frameAttachRequested' }` via window.jarvisBus.send.
- ChatInput.tsx: imports CameraButton and renders it in the toolbar row
between the input field and the Send button.
- chat-panel.css: `.camera-button` styling, matches `.chat-input__send`
chrome so the toolbar reads as one row.
Tests: +3 (CameraButton.test.tsx)
- CB1: renders accessible button (aria-label "Attach camera frame").
- CB2: click posts the BusInbound.frameAttachRequested message.
- CB3: payload shape is `{ type: 'frameAttachRequested' }` only —
matches the no-associated-value Swift case in BusInbound.swift.
JS-side path: webview/packages/hud/src/chat/CameraButton.tsx:14
→ window.jarvisBus.send({ type: 'frameAttachRequested' })
Swift-side path: App/AppDelegate.swift:1696-1698
→ frameAttachController.requestAttach(reason: .hudButton)
HUD vitest 94 → 97. Pre-existing SegmentedRing typecheck warnings are
unrelated (verified via git stash on a clean tree).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>