--- phase: 04-usb-manager-label-printing plan: "05" subsystem: frontend tags: [usb, sse, react, label-printing, real-time] dependency_graph: requires: [04-03] provides: [USB-04-frontend] affects: [DashboardPage, ItemCard, ItemRow] tech_stack: added: [] patterns: [EventSource SSE, react-hot-toast, lucide-react icons] key_files: created: - web/src/hooks/useUSBEvents.ts - web/src/components/USBStatusBar.tsx modified: - web/src/lib/api.ts - web/src/components/inventory/ItemCard.tsx - web/src/components/inventory/ItemRow.tsx - web/src/pages/DashboardPage.tsx decisions: - Print button added to ItemCard and ItemRow directly (not DashboardPage map) — cleaner separation of concerns - handlePrintLabel defined at module level outside component to avoid per-render allocation - USBStatusBar placed in DashboardPage header (flex justify-between alongside title) - No ESLint config exists in project — skipped ESLint step, TypeScript + build used as verification metrics: duration: 8m completed: 2026-04-10 tasks_completed: 2 files_changed: 6 --- # Phase 04 Plan 05: Frontend USB Status Bar and Print Label Summary One-liner: SSE-backed useUSBEvents hook with USBStatusBar component and per-item print label button wired to POST /api/labels/:id/print with react-hot-toast feedback. ## Tasks Completed | Task | Description | Commit | |------|-------------|--------| | 1 | useUSBEvents hook + USBStatusBar component | 8b88970 | | 2 | Wire USBStatusBar + Print Label into dashboard | 3de1e4f | ## What Was Built ### useUSBEvents hook (`web/src/hooks/useUSBEvents.ts`) - Subscribes to `GET /api/usb/events` SSE stream via `new EventSource('/api/usb/events')` - Maintains `connectedDevices: Map` in React state - Connect events add to map; disconnect events remove from map - Malformed JSON silently ignored (T-04-14 mitigated) - EventSource auto-reconnects on error (T-04-15 accepted) - Exports: `useUSBEvents`, `DeviceEvent`, `DeviceSpec`, `StateConnected`, `StateDisconnected` ### USBStatusBar component (`web/src/components/USBStatusBar.tsx`) - Consumes `useUSBEvents()`, renders connected device list - Role icons: Printer (role=0), Cable (role=1), Usb (role=2/unknown) - Green dot per connected device with device name - Empty state: "No USB devices" with USB icon in muted text - ClickHouse design: black bg, `border-white/10`, `text-white/80`, `bg-green-500` status dots ### api.ts additions (`web/src/lib/api.ts`) - `printLabel(deviceId: number): Promise` — POST /api/labels/:id/print - `PrintLabelResponse` interface: `{ status: string; print_skipped?: boolean }` - Throws typed Error on non-OK response ### ItemCard (`web/src/components/inventory/ItemCard.tsx`) - Module-level `handlePrintLabel(e, itemId)` using `printLabel()` from api.ts - Printer icon button in CardFooter alongside NetBox link - Toast: loading → success / "queued — printer not connected" / error ### ItemRow (`web/src/components/inventory/ItemRow.tsx`) - Same `handlePrintLabel` pattern - Print button added to quick-actions group (appears on hover with ExternalLink) - Opacity transition matches existing group-hover pattern ### DashboardPage (`web/src/pages/DashboardPage.tsx`) - Imports and renders `` in page header (flex row, right-aligned) - No layout restructuring — additive change only ## Deviations from Plan ### Auto-fixed Issues None — plan executed exactly as written with one minor deviation: **1. [Rule 2 - Deviation] Print button added to ItemCard/ItemRow rather than DashboardPage map** - **Found during:** Task 2 - **Reason:** DashboardPage passes items to `` and `` as components — adding print logic inline in the map would duplicate code and break component encapsulation. Plan said "find where item quick actions are rendered" which is the card/row components. - **Impact:** Cleaner architecture; identical user-visible result - **Files modified:** ItemCard.tsx, ItemRow.tsx (instead of DashboardPage.tsx map) ## Known Stubs None — all data flows wired. USBStatusBar connects to live SSE. Print button calls real endpoint. ## Threat Flags None — no new trust boundaries beyond those in the plan's threat model. ## Self-Check Files exist: - web/src/hooks/useUSBEvents.ts — FOUND - web/src/components/USBStatusBar.tsx — FOUND - web/src/lib/api.ts (modified) — FOUND - web/src/components/inventory/ItemCard.tsx (modified) — FOUND - web/src/components/inventory/ItemRow.tsx (modified) — FOUND - web/src/pages/DashboardPage.tsx (modified) — FOUND Commits: - 8b88970 — feat(04-05): add useUSBEvents hook and USBStatusBar component - 3de1e4f — feat(04-05): wire USBStatusBar and Print Label button into dashboard Build: `npm run build` — PASSED (tsc -b + vite build, 0 errors, 1965 modules) ## Self-Check: PASSED