homelabby/.planning/phases/05-cable-test-integration/05-03-PLAN.md
2026-04-10 07:04:57 +00:00

9.7 KiB

phase plan type wave depends_on files_modified autonomous requirements must_haves
05-cable-test-integration 03 execute 3
05-02
web/src/pages/CableTestPage.tsx
web/src/pages/CableTestPage.test.tsx
web/src/api/test.ts
web/src/router.tsx
true
CBL-06
truths artifacts key_links
Navigating to /test renders the Cable Test Station page
Left panel shows active tester readout (or 'No tester connected' placeholder)
Right panel shows last 20 tests in a scrollable list
Center panel shows label preview for the most recent test
Print & Next button POSTs the current test result and clears the form for the next cable
Live FNB58 data streams into the tester readout panel via GET /api/test/events SSE
Page is usable on mobile screen (single column stacked layout below lg breakpoint)
path provides
web/src/pages/CableTestPage.tsx Cable Test Station page with three panels
path provides
web/src/api/test.ts submitCableTest(), streamTestEvents(), getRecentTests() API helpers
path provides
web/src/router.tsx /test route registered
from to via
Print & Next button POST /api/test/cable submitCableTest() mutation (TanStack Query useMutation)
from to via
Live readout panel GET /api/test/events EventSource in useEffect, closed on unmount
from to via
Recent tests panel GET /api/test/recent useQuery with 5s refetch interval
Build the Cable Test Station page at /test: three-panel layout (tester readout, label preview, recent tests), Print & Next workflow, live SSE updates, mobile-responsive.

Purpose: Operator can test cables and print labels from a single page without leaving the workflow. Output: web/src/pages/CableTestPage.tsx, web/src/api/test.ts, router update.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/05-cable-test-integration/05-CONTEXT.md @.planning/phases/05-cable-test-integration/05-02-SUMMARY.md @.planning/phases/03-dashboard-intake-ui/03-01-SUMMARY.md

@web/src/router.tsx @web/src/pages/DashboardPage.tsx

POST /api/test/cable Request body:

{
  "cable_type": 0,
  "usb_version": "USB 3.2 Gen 2",
  "dp_version": "",
  "hdmi_version": "",
  "speed_gbps": 10.0,
  "max_watts": 100,
  "pin_continuity": true,
  "has_emarker": true,
  "resistance_ohm": 0.12,
  "hw_id": "HW-00042"
}

Response 201:

{ "hw_id": "HW-00042", "netbox_id": 7, "print_skipped": false }

GET /api/test/recent Response 200: array of TestResult objects (same shape as POST body)

GET /api/test/events (SSE) event stream: data: {"voltage":5.1,"current_amps":3.0,"power_watts":15.3,"pd_protocol":"PD3.0","timestamp":"..."}\n\n

Design system tokens (from CLAUDE.md / ClickHouse design):

  • Background: #000000
  • Accent: #faff69 (neon volt)
  • Card background: #111111
  • Border: #222222
  • Text primary: #ffffff
  • Text muted: #888888
  • Tailwind classes: bg-black, text-[#faff69], bg-[#111], border-[#222]
Task 1: API helpers + Cable Test Station page web/src/api/test.ts, web/src/pages/CableTestPage.tsx, web/src/router.tsx Create web/src/api/test.ts: ```typescript export interface TestResult { cable_type: 0 | 1 | 2; // 0=USB, 1=DP, 2=HDMI usb_version: string; dp_version: string; hdmi_version: string; speed_gbps: number; max_watts: number; pin_continuity: boolean; has_emarker: boolean; resistance_ohm: number; hw_id: string; }
export interface SubmitResponse {
  hw_id: string;
  netbox_id: number;
  print_skipped: boolean;
}

export interface LiveReading {
  voltage: number;
  current_amps: number;
  power_watts: number;
  pd_protocol: string;
  timestamp: string;
}

export async function submitCableTest(result: TestResult): Promise<SubmitResponse> {
  const res = await fetch('/api/test/cable', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(result),
  });
  if (!res.ok) throw new Error(`submit failed: ${res.status}`);
  return res.json();
}

export async function getRecentTests(): Promise<TestResult[]> {
  const res = await fetch('/api/test/recent');
  if (!res.ok) throw new Error(`recent tests failed: ${res.status}`);
  return res.json();
}
```

Create web/src/pages/CableTestPage.tsx:

Layout: Three-column grid on lg+, single stacked column on mobile.
- Column 1 (tester readout): "Cable Test Station" heading; if liveReading state is set show Voltage/Current/Power/Protocol values with neon volt accent; else show "No tester connected" in muted text. Use EventSource in useEffect — close on component unmount. On each SSE message parse JSON into liveReading state.
- Column 2 (center — label preview + print): Show a card with current test fields as text (HW ID, type, version, speed, power, continuity, eMarker, resistance). "Print & Next" button (bg-[#faff69] text-black font-bold): calls submitCableTest mutation; on success show toast "Label printed for {hw_id}" (react-hot-toast) then reset form. If print_skipped=true toast says "Saved — printer not available". Loading state: button disabled + spinner.
- Column 3 (recent tests): "Recent Tests" heading; useQuery fetching getRecentTests() every 5000ms; render as list rows showing cable_type icon (USB/DP/HDMI), hw_id, pin_continuity chip (green tick / red X), speed_gbps.

Use lucide-react icons: Usb, Monitor, Tv2 for cable types. CheckCircle2 and XCircle for continuity.

For the "current test" form: a simple controlled form with number/text inputs for each TestResult field. Pre-populate with mock data when liveReading arrives (voltage/current into the readout; the test fields remain manual until real driver parsing is implemented).

Register /test route in web/src/router.tsx. Add "Test" nav link in the site nav (alongside Dashboard / Intake / Scan).

Design tokens: all backgrounds bg-black, cards bg-[#111] border border-[#222] rounded-lg p-4, headings text-white, muted text-[#888], accent text-[#faff69].
cd /home/mikkel/homelabby/web && npm run build 2>&1 | tail -20 npm run build exits 0; /test route exists in router; CableTestPage renders three panels; Print & Next button is present. Task 2: Unit tests for CableTestPage web/src/pages/CableTestPage.test.tsx Create web/src/pages/CableTestPage.test.tsx using Vitest + React Testing Library (already in the project from Phase 3).
Mock fetch globally with vi.stubGlobal.

Tests:
1. "renders 'No tester connected' when no SSE data" — render CableTestPage, assert text present.
2. "renders recent tests list" — mock getRecentTests() returning 2 USB items; assert two rows visible.
3. "Print & Next calls submitCableTest and shows toast" — mock submitCableTest to resolve {hw_id:"HW-00001",netbox_id:1,print_skipped:false}; fill hw_id input; click Print & Next; await toast text "Label printed".
4. "Print & Next shows print_skipped toast" — mock returns print_skipped:true; assert toast says "Saved — printer not available".

Mock EventSource in test setup:
```typescript
class MockEventSource {
  addEventListener = vi.fn();
  removeEventListener = vi.fn();
  close = vi.fn();
}
vi.stubGlobal('EventSource', MockEventSource);
```

Wrap component in necessary providers (QueryClientProvider, MemoryRouter, Toaster).
cd /home/mikkel/homelabby/web && npm test -- --run CableTestPage 2>&1 | tail -30 All 4 CableTestPage tests pass; npm run build still exits 0.

<threat_model>

Trust Boundaries

Boundary Description
browser → POST /api/test/cable User-submitted form data; validated server-side in 05-02
SSE stream → browser Server push only; EventSource is read-only, no injection risk

STRIDE Threat Register

Threat ID Category Component Disposition Mitigation Plan
T-05-07 XSS TestResult fields rendered in DOM mitigate Use React JSX (auto-escaped); no dangerouslySetInnerHTML
T-05-08 DoS EventSource leak on unmount mitigate useEffect cleanup closes EventSource; verified in test 1
</threat_model>
``` cd web && npm run build # exits 0, no TypeScript errors cd web && npm test -- --run CableTestPage # all 4 tests pass # Manual smoke test: navigate to http://localhost:5173/test # Verify three panels render, Print & Next button present, /test in nav ```

<success_criteria>

  • /test route renders Cable Test Station page with three panels
  • Print & Next submits POST /api/test/cable, shows success toast, resets form
  • SSE live readings update the tester readout panel; EventSource closed on unmount
  • Recent tests list auto-refreshes every 5s
  • Layout is single-column on mobile, three-column on lg+
  • All 4 unit tests pass; npm run build exits 0
  • ClickHouse design tokens applied throughout (#000000 background, #faff69 accent) </success_criteria>
After completion, create `.planning/phases/05-cable-test-integration/05-03-SUMMARY.md`