---
phase: 09-ux-tech-debt-closure
plan: "01"
subsystem: ui
tags: [fastapi, htmx, jinja2, oob-swap, driver-upload, pytest]

# Dependency graph
requires:
  - phase: 03-printer-configuration
    provides: printer_form.html with Alpine.js x-data and driver <select>
  - phase: 04-driver-management
    provides: upload_driver handler, DriverStore, Driver model
provides:
  - HTMX OOB swap: POST /drivers/upload emits driver_list + printer-form-driver-select refresh
  - Inline driver upload form inside printer form (sibling, not nested)
  - Stable id="printer-form-driver-select" on driver <select> for OOB targeting
  - caller=printer_form sentinel-based OOB branching in upload handler
  - 4 new integration tests (500 regression x2 + OOB contract x2) + 1 printer form test
affects:
  - 09-02-playwright-port-autofill (depends on final printer_form.html shape)
  - 10-rtval (runtime validation uses driver upload flow)

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "HTMX OOB swap via hx-swap-oob=\"true\" on sibling element in same response body"
    - "Caller-context sentinel: hidden form field name=caller value=printer_form"
    - "FastAPI mixed multipart: UploadFile + Form() parameters in same handler"
    - "TDD RED-GREEN: write failing tests, diagnose from output, implement fix"

key-files:
  created:
    - imptune/templates/partials/driver_upload_with_oob.html
    - tests/test_printer_form.py
  modified:
    - imptune/api/drivers.py
    - imptune/templates/partials/printer_form.html
    - tests/test_driver_upload.py

key-decisions:
  - "Sentinel field (caller=printer_form) chosen over HX-Target header for caller detection — explicit and testable without HTTP header manipulation"
  - "OOB template includes driver_list.html as primary swap + sibling <select> with hx-swap-oob — clean separation of concerns"
  - "Inline upload form placed as sibling after </form>, within Alpine x-data div — required by HTML spec (no nested forms)"
  - "Hidden #driver-list anchor added to printer form page — provides HTMX outerHTML swap target without full driver list UI on the form"

patterns-established:
  - "Pattern: HTMX OOB via separate template (driver_upload_with_oob.html) includes primary fragment + appends OOB elements"
  - "Pattern: caller-aware handler branches on form field, not HTTP header"

requirements-completed: [UX-01]

# Metrics
duration: 5min
completed: "2026-04-13"
---

# Phase 09 Plan 01: Driver Upload Fix and Inline OOB Summary

**HTMX OOB driver-select refresh on upload: POST /drivers/upload now emits hx-swap-oob select when caller=printer_form, with printer form wired as sibling inline upload form**

## Performance

- **Duration:** ~5 min
- **Started:** 2026-04-13T08:46:30Z
- **Completed:** 2026-04-13T08:51:30Z
- **Tasks:** 3 (TDD: 2 TDD tasks + 1 template wiring task)
- **Files modified:** 5

## Accomplishments

- Fixed `POST /drivers/upload` caller-awareness: handler now accepts `caller: str = Form("")` parameter and branches on `caller == "printer_form"` to emit OOB-enabled response
- Created `driver_upload_with_oob.html` template: primary `#driver-list` fragment + sibling `<select hx-swap-oob="true" id="printer-form-driver-select">` with new driver auto-selected
- Wired inline driver upload form in `printer_form.html`: sibling `<form hx-post="/drivers/upload">` with `caller=printer_form` sentinel, outside the printer `<form>` to comply with HTML spec
- Added stable `id="printer-form-driver-select"` to driver `<select>` for OOB targeting
- Delivered 5 new tests: 2 parametrized 500 regression variants, 2 OOB contract tests, 1 printer form wiring test

## Task Commits

1. **Task 1: Write failing driver-upload regression + OOB contract tests** - `d1de839` (test)
2. **Task 2: Fix handler + add OOB template** - `10ee09a` (fix)
3. **Task 3: Wire inline upload form into printer_form.html** - `72c6a98` (feat)

## Files Created/Modified

- `imptune/api/drivers.py` - Added Form import, caller parameter, new_driver capture, OOB branch
- `imptune/templates/partials/driver_upload_with_oob.html` - New template: primary fragment include + OOB select
- `imptune/templates/partials/printer_form.html` - Stable select id, sibling upload form, hidden driver-list anchor
- `tests/test_driver_upload.py` - 4 new tests: 500 regression (x2 parametrized), OOB contract, no-OOB-on-standalone
- `tests/test_printer_form.py` - New file: test_printer_form_has_inline_driver_upload

## Decisions Made

- Sentinel field `caller=printer_form` chosen over `HX-Target` header — simpler, more explicit, testable without HTTP header manipulation in tests
- OOB template uses `{% include "partials/driver_list.html" %}` to avoid duplication; OOB select appended as sibling after the include
- Hidden `<div id="driver-list" style="display:none">` added to printer form to provide HTMX outerHTML swap target — keeps driver list hidden on printer form but enables HTMX to find the target
- Upload form placed AFTER `</form>` of the printer form but inside the Alpine `x-data` div — avoids invalid HTML nested forms while preserving Alpine scope

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 3 - Blocking] test_upload_500_regression passed without a real 500 repro**

- **Found during:** Task 1 (writing RED tests)
- **Issue:** The synthetic ZIP fixture doesn't reproduce the 500 that was reported. Both parametrized variants (plain UTF-8 and BOM/UTF-16 LE) returned 200. The handler was already robust enough for these cases.
- **Fix:** Kept both variants as documented regression guards. The 500 was pre-surfaced as a concern from Phase 8 kickoff; adding regression coverage is still correct even if the synthetic fixture doesn't repro it. The OOB tests were RED (the actual broken behavior).
- **Files modified:** tests/test_driver_upload.py (kept parametrized variants)
- **Verification:** 2 OOB tests went RED as expected; 500 variants green (correct behavior)
- **Committed in:** d1de839 (Task 1 test commit)

---

**Total deviations:** 1 auto-handled (plan expected 500 to repro; it didn't — OOB tests were the actual failures driving the fix)
**Impact on plan:** No scope change. Both the regression guard and OOB fix were delivered. Handler correctly returns 200 for tested fixtures.

## Issues Encountered

- Python bytes concatenation error in initial test code (`b"\xff\xfe" + str` instead of `b"\xff\xfe" + str.encode()`). Fixed inline before committing.
- File was overwritten by linter between edits; used `cat >>` bash append to reliably add new test functions to the file.

## Next Phase Readiness

- `printer_form.html` is in its final shape for 09-02 (Playwright test can assert Alpine port autofill against this version)
- OOB driver upload flow is fully wired and test-covered
- Full non-e2e suite: 112 passed, 0 failures

---
*Phase: 09-ux-tech-debt-closure*
*Completed: 2026-04-13*
