# Phase 9: UX Tech Debt Closure - Context
**Gathered:** 2026-04-13
**Status:** Ready for planning
## Phase Boundary
Close the three carried-over UX defects from v1.0 (UX-01, UX-02, UX-03) so the build rolled out in Phase 11 is the polished one technicians actually use. Also fix the `POST /drivers/upload` HTTP 500 surfaced during Phase 8 kickoff — it's a hard blocker for UX-01 and is bundled into that work. No new product capabilities, no refactors outside the touched files.
## Implementation Decisions
### Plan structure (3 plans, sequential)
- **09-01 — UX-01 + driver upload 500 fix (bundled).** Reproduce the 500, write a failing pytest first (TDD), fix root cause, then add inline driver upload input to the printer form template, wire HTMX OOB swap so the printer form's `
## Existing Code Insights
### Reusable Assets
- **[imptune/api/drivers.py:35-115](imptune/api/drivers.py#L35-L115)** — `upload_driver` handler. Already returns HTMX partials and uses `_error_response()` for validated 400s. Extend to emit OOB fragment for printer-form callers; find and fix the uncaught exception causing the 500.
- **[imptune/templates/partials/driver_list.html](imptune/templates/partials/driver_list.html)** — existing partial returned by upload. Inspect to understand current structure before layering OOB output.
- **[imptune/templates/partials/printer_form.html:29-39](imptune/templates/partials/printer_form.html#L29-L39)** — driver ``. Target for OOB swap. The whole label block (or just the ``) becomes the OOB-swap root.
- **[imptune/templates/partials/printer_form.html:1](imptune/templates/partials/printer_form.html#L1)** — existing Alpine.js `x-data` block. Add upload control inside the same form scope to share Alpine state if needed.
- **[imptune/templates/printer_detail.html:48-51](imptune/templates/printer_detail.html#L48-L51)** — Export section where script download links will be added.
- **Script generators under `imptune/services/`** — plain-string contract per PROJECT.md Key Decisions; safe to call from new routes without DB coupling.
- **`tests/test_driver_upload.py`** — existing test file, extend with the 500 regression case.
- **`imptune/api/printers.py`** — existing printer router, candidate home for the 3 new script download routes.
### Established Patterns
- **HTMX partial responses with HTTP 4xx for validated failures** (`_error_response()`) — reuse pattern for any new validation in the inline-upload path.
- **Jinja2 templates served via `fastapi.templating.Jinja2Templates(directory=templates_dir)`** — add new blocks, no router-level template refactor needed.
- **Content-addressed storage (SHA256)** for drivers — no change; the 500 bugfix should not alter this.
- **Alpine.js `x-data` inline state** in printer_form.html — pattern for new inline-upload local state (e.g., `uploading: false`).
- **Existing pytest + httpx test pattern** per PROJECT.md — 500 repro test follows same structure.
### Integration Points
- **OOB swap contract:** The HTMX OOB fragment must use `` (or equivalent); requires adding a stable `id` to the `` in printer_form.html if it doesn't have one.
- **Playwright integration:** New `tests/e2e/` (or similar) subdir holding Playwright specs. Pytest invokes them via `pytest-playwright` plugin. App must be reachable on a test port during the run — fixture starts FastAPI via uvicorn in a subprocess or thread.
- **Script route dependency:** New download routes need to load `Printer` by id and call the same generator functions package export uses. No new business logic, pure re-use.
- **No source files outside `imptune/api/`, `imptune/templates/`, and `tests/` should need editing** for this phase (plus `requirements-dev.txt` for Playwright).
## Specific Ideas
- User re-framed UX-01 mid-discussion: adding an inline upload to the printer form was initially flagged as scope creep, but re-reading REQUIREMENTS.md UX-01 confirmed the requirement literally says "uploaded *on the printer form*" — so the inline upload IS the ask, not an add-on.
- User explicitly chose auto-select-new-driver behavior after upload ("Dropdown shows + auto-selects new") — the common case is "I uploaded this driver *for this printer*", so save the click.
- User treats the 500 as non-negotiable ("Yes — must be fixed") regardless of whether it's edge-case or happy-path. No conditional shipping.
- TDD is required for the 500 fix — failing test first, red-green-commit, not fix-then-test.
- Playwright chosen over manual screenshot because a committed test survives template edits; a screenshot doesn't.
- Three distinct GET routes preferred over `?kind=` for discoverability and clean `Content-Disposition` filenames.
## Deferred Ideas
- **Hardening `/drivers/upload` against unknown future exceptions** (global try/except, structured error pages) — out of scope. Fix the reported 500 only; broader resilience is v1.2 territory.
- **A "Download all scripts as ZIP" button** alongside the 3 individual links — tempting but not asked for by UX-03. Log for v1.2 if technicians request it after rollout.
- **Playwright tests for anything beyond UX-02** (e.g., full printer CRUD flow, driver upload E2E) — Phase 9 adds Playwright only for UX-02; expanding coverage is a Phase 11 feedback-driven decision.
- **Refactoring `upload_driver` into a service layer** to separate validation from HTTP concerns — not needed for this fix; avoid scope creep.
- **Fixing `/drivers/upload` to return JSON for programmatic clients** — v1.0 is HTMX-only; no programmatic consumers exist.
---
*Phase: 09-ux-tech-debt-closure*
*Context gathered: 2026-04-13*