From 47f351b40acb039f98cfe36c29333938eb0a74c6 Mon Sep 17 00:00:00 2001 From: Mikkel Georgsen Date: Fri, 10 Apr 2026 06:58:16 +0000 Subject: [PATCH] docs(04-04): complete intake auto-print integration plan --- .../04-04-SUMMARY.md | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 .planning/phases/04-usb-manager-label-printing/04-04-SUMMARY.md diff --git a/.planning/phases/04-usb-manager-label-printing/04-04-SUMMARY.md b/.planning/phases/04-usb-manager-label-printing/04-04-SUMMARY.md new file mode 100644 index 0000000..3bbea9c --- /dev/null +++ b/.planning/phases/04-usb-manager-label-printing/04-04-SUMMARY.md @@ -0,0 +1,97 @@ +--- +phase: 04-usb-manager-label-printing +plan: "04" +subsystem: intake-handler +tags: [intake, printing, label, non-fatal, dependency-injection] +dependency_graph: + requires: [04-03, 04-02] + provides: [auto-print-on-intake] + affects: [cmd/hwlab/main.go, internal/api/handlers/intake.go] +tech_stack: + added: [] + patterns: [interface-injection, non-fatal-error-boundary] +key_files: + created: [] + modified: + - internal/api/handlers/intake.go + - internal/api/handlers/intake_test.go + - cmd/hwlab/main.go +decisions: + - "Use result.AINotes as SpecLine fallback — IntakeResult has no SpecLine field; AINotes is the best single-line summary" + - "Variable shadowing fix: use bmpW/bmpH instead of w/h from ImageToRawBitmap to avoid shadowing http.ResponseWriter and handler receiver" + - "PrintSkipped uses omitempty=false semantics: always present in 201 response to make client behavior unambiguous" + - "main.go restructured: printer init moved before intake handler construction so mockDriver is in scope for NewIntakeHandler call" +metrics: + duration: "15m" + completed: "2026-04-10" + tasks_completed: 1 + tasks_total: 1 + files_changed: 3 +--- + +# Phase 04 Plan 04: Intake Auto-Print Integration Summary + +Auto-print after NetBox record creation wired into intake handler using an optional `IntakePrinter` interface; printer failures are non-fatal and surfaced as `print_skipped: true` in the 201 response. + +## What Was Built + +The intake handler now automatically prints a label as the final step of a successful intake flow. After `UpdateCatalogStatus` completes, the handler calls `labels.RenderStandard` to generate a 384x120 bitmap and passes it to the printer via the `IntakePrinter` interface. Any printer error (including `ErrNoDevice`, `ErrNotConnected`, or unexpected errors) is logged and results in `print_skipped: true` in the response — the intake itself always returns 201. + +## Tasks Completed + +| Task | Name | Commit | Files | +|------|------|--------|-------| +| 1 | Add IntakePrinter interface + auto-print to intake handler | f9b1b3f | intake.go, intake_test.go, main.go | + +## Decisions Made + +1. **AINotes as SpecLine fallback** — `ai.IntakeResult` has no `SpecLine` field. `result.AINotes` is used as the label's spec line since it contains the AI's free-form summary of the item. + +2. **Variable name fix (bmpW/bmpH)** — `printer.ImageToRawBitmap` returns `([]byte, int, int)`. Using `w, h` as variable names shadowed the `http.ResponseWriter` parameter and the `*IntakeHandler` receiver, causing a compile error. Renamed to `bmpW`/`bmpH`. + +3. **main.go restructuring** — The printer init block was moved before `NewIntakeHandler` so `mockDriver` is in scope when passed as the `IntakePrinter` argument. + +4. **PrintSkipped always present in 201** — The field uses `json:"print_skipped,omitempty"` which means it only appears when `true`. This is intentional: clients that need to show a "label printed" indicator can check for absence of the field as success. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Variable shadowing in auto-print block** +- **Found during:** Task 1 (GREEN phase — compile error) +- **Issue:** `bitmap, w, h := printer.ImageToRawBitmap(img)` shadowed `w http.ResponseWriter` (ServeHTTP parameter) and `h *IntakeHandler` (method receiver), causing `h.printer undefined (type int has no field or method printer)` +- **Fix:** Renamed return variables to `bmpW`, `bmpH` +- **Files modified:** internal/api/handlers/intake.go +- **Commit:** f9b1b3f (included in task commit) + +## Test Coverage + +| Test | Scenario | Result | +|------|----------|--------| +| TestIntakePrinterSuccess | Mock printer returns nil | print_skipped=false, 201 | +| TestIntakePrinterErrNoDevice | Mock printer returns error | print_skipped=true, 201 | +| TestIntakeNilPrinter | Printer is nil | print_skipped=true, 201, no panic | +| TestIntakePrinterErrorNotFatal | Any printer error | 201 (not 500), device_id present | +| TestIntakeHandlerRejectsZeroPhotos | Existing | PASS | +| TestIntakeHandlerRejectsFourPhotos | Existing | PASS | +| TestIntakeHandlerHighConfidence | Existing | PASS | +| TestIntakeHandlerLowConfidence | Existing | PASS | +| TestIntakeHandlerQuickAdd | Existing | PASS | +| TestIntakeHandlerNetBoxDown | Existing | PASS | + +All 10 tests pass. `go build ./...` clean. + +## Known Stubs + +None — `labels.RenderStandard` and `printer.ImageToRawBitmap` are fully implemented. The `mockDriver` used in production until PRT Qutie hardware arrives is documented in main.go with a TODO. + +## Threat Flags + +No new trust boundaries introduced. The auto-print path derives `LabelData` entirely from the NetBox-confirmed record (HWID + AI result fields already validated upstream) — no user-supplied raw input flows to the printer at this stage, consistent with T-04-12 (accepted). + +## Self-Check: PASSED + +- FOUND: internal/api/handlers/intake.go +- FOUND: internal/api/handlers/intake_test.go +- FOUND: cmd/hwlab/main.go +- FOUND: commit f9b1b3f