# Phase 15: UX Driver Upload Feedback Fix - Research **Researched:** 2026-04-16 **Domain:** HTMX OOB swap, Jinja2 templates, driver upload flow **Confidence:** HIGH ## Summary The integration gap is fully diagnosed by reading the actual source files. No speculative work is needed. Phase 9 (plan 09-01) wired the driver upload OOB flow correctly: `POST /drivers/upload?caller=printer_form` returns `driver_upload_with_oob.html`, which contains two swap targets: (1) the primary `#driver-list` `outerHTML` swap, and (2) an OOB `... | HTMX processes response: - Primary: replaces
in DOM with the returned div - OOB: replaces ... ``` The `driver_list.html` partial renders `
` — this is the primary swap content. It is returned as the response body. The `
``` ### Fix (single-line change) ```html
``` ### Upload Handler (unchanged — already correct) ```python # Source: imptune/api/drivers.py, lines 113-122 if caller == "printer_form": return templates.TemplateResponse( request=request, name="partials/driver_upload_with_oob.html", context={ "driver_data": driver_data, "new_driver_id": new_driver.id, "parsed": parsed, }, ) ``` ### OOB Template (unchanged — already correct) ```html {% include "partials/driver_list.html" %} ``` ### Test Pattern (existing style from test_printer_form.py and test_driver_upload.py) ```python # Pattern: POST upload with caller, check HTML for confirmation content def test_upload_feedback_visible_on_printers_new(client: TestClient) -> None: """After upload on /printers/new flow, driver name appears in the OOB response fragment.""" zip_bytes = _make_driver_zip_with_cat() resp = client.post( "/drivers/upload", files={"file": ("driver.zip", zip_bytes, "application/zip")}, data={"caller": "printer_form"}, ) assert resp.status_code == 200 # The primary #driver-list fragment must contain the driver name (upload confirmation) assert "Test LaserJet Pro" in resp.text # OOB select still refreshes assert 'hx-swap-oob="true"' in resp.text ``` Note: this test verifies server-side response content (what HTMX receives). The visibility fix (removing `style="display:none"`) is a template change verified by inspecting the rendered `/printers/new` HTML. ```python def test_printers_new_driver_list_visible(client: TestClient) -> None: """GET /printers/new: #driver-list anchor must NOT have display:none.""" resp = client.get("/printers/new") assert resp.status_code == 200 assert 'id="driver-list"' in resp.text # The anchor must exist but must NOT be hidden assert 'id="driver-list" style="display:none"' not in resp.text ``` ## State of the Art | Old Approach | Current Approach | When Changed | Impact | |--------------|------------------|--------------|--------| | Inline form on /printers | Dedicated /printers/new page | Phase 11 | printers_new.html is the only template to fix | | printer_form.html partial on /printers | Inlined form markup in printers_new.html | Phase 11 | Two diverged templates; fix goes in printers_new.html only | | No OOB upload flow | caller=printer_form sentinel + driver_upload_with_oob.html | Phase 9 | OOB mechanism works; visibility is the only gap | **Deprecated/outdated:** - The Phase 9 plan notes about adding a hidden anchor: correct at the time, but the hidden anchor is now the bug in the Phase 11 context. ## Open Questions 1. **Should `printer_form.html` also get its hidden anchor removed?** - What we know: `printer_form.html` is no longer included in `/printers/new` (Phase 11 inlined the markup). It may still be used in other contexts (e.g. edit modal). - What's unclear: whether any route still renders `printer_form.html` as a standalone partial. - Recommendation: Check if `printer_form.html` is referenced anywhere besides the edit modal. Scope of Phase 15 is `printers_new.html` only — do not change `printer_form.html` unless the edit modal context also needs visible feedback. 2. **i18n key for "Available Drivers" heading** - What we know: Phase 12 added full i18n coverage; all strings use Alpine i18n store. - What's unclear: whether an i18n key for a "Drivers uploaded" or "Available Drivers" label already exists. - Recommendation: Check `imptune/static/i18n/` for existing keys before adding a new one. If a label is out of scope, omit it and just un-hide the div — the driver list table is self-explanatory. ## Validation Architecture ### Test Framework | Property | Value | |----------|-------| | Framework | pytest (project standard) | | Config file | `pyproject.toml` or `pytest.ini` (inferred from project) | | Quick run command | `pytest tests/test_printer_form.py tests/test_driver_upload.py -x -q` | | Full suite command | `pytest tests/ -x -q --ignore=tests/e2e` | ### Phase Requirements -> Test Map | Req ID | Behavior | Test Type | Automated Command | File Exists? | |--------|----------|-----------|-------------------|-------------| | UX-01 | Upload confirmation visible after driver upload on /printers/new | integration | `pytest tests/test_printer_form.py -x -q` | Partial — new test needed | | UX-01 | #driver-list anchor not hidden on GET /printers/new | integration | `pytest tests/test_printer_form.py -x -q` | Partial — new assertion needed | | UX-01 | OOB select still refreshes (no regression) | integration | `pytest tests/test_driver_upload.py::test_upload_returns_oob_when_called_from_form -x` | YES (test_driver_upload.py) | ### Sampling Rate - **Per task commit:** `pytest tests/test_printer_form.py tests/test_driver_upload.py -x -q` - **Per wave merge:** `pytest tests/ -x -q --ignore=tests/e2e` - **Phase gate:** Full suite green before marking phase complete ### Wave 0 Gaps - [ ] `tests/test_printer_form.py` — add `test_printers_new_driver_list_visible` (asserts no `style="display:none"` on `#driver-list`) - [ ] `tests/test_printer_form.py` — add `test_upload_feedback_visible_on_printers_new` (asserts driver name appears in upload response) *(Existing test infrastructure covers everything else — only these two assertions are missing)* ## Sources ### Primary (HIGH confidence) - `imptune/templates/printers_new.html` — confirmed `