16 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 09-ux-tech-debt-closure | 01 | execute | 1 |
|
true |
|
|
Purpose: Closes UX-01 — technicians uploading a driver while creating/editing a printer see it appear in the dropdown and get it auto-selected, no manual reload. Output: Green regression + OOB tests, a working inline upload control, and an OOB-capable upload handler.
<execution_context> @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/09-ux-tech-debt-closure/09-CONTEXT.md @.planning/phases/09-ux-tech-debt-closure/09-RESEARCH.md @.planning/phases/09-ux-tech-debt-closure/09-VALIDATION.md @imptune/api/drivers.py @imptune/templates/partials/printer_form.html @imptune/templates/partials/driver_list.html @tests/test_driver_upload.py @tests/conftest.pyFrom imptune/api/drivers.py:
router = APIRouter(prefix="/drivers") # mounted at /drivers in main.py
MAX_UPLOAD_BYTES = 100 * 1024 * 1024
def _error_response(message: str, status_code: int = 400) -> HTMLResponse: ...
@router.post("/upload", response_class=HTMLResponse)
def upload_driver(request: Request, file: UploadFile) -> HTMLResponse: ...
# Validates zip, parses INF via parse_inf, persists via DriverStore(_cfg.DRIVERS_DIR),
# upserts Driver via Driver.get_or_create(sha256=..., defaults={...}),
# returns TemplateResponse("partials/driver_list.html", {driver_data, parsed}).
# There is NO try/except around parse_inf / DriverStore.save / Driver.get_or_create —
# any of these can bubble into a FastAPI 500.
From imptune/db/models.Driver: fields include id, sha256, original_filename, driver_desc (JSON list), inf_filename, architecture, has_cat_file, uploaded_at.
From imptune/services/inf_parser: parse_inf(inf_text, inf_filename, zip_names) -> ParsedInf with attributes driver_names: list[str], inf_filename: str, architecture: str | None, has_cat_file: bool.
Current printer_form.html driver select (lines 28-39):
<label>
Driver
<select name="driver_id"> <!-- NO id attribute today -->
<option value="">-- No driver --</option>
{% for item in driver_data %}
<option value="{{ item.driver.id }}"
{% if printer and printer.driver_id == item.driver.id %}selected{% endif %}>
{{ item.driver.original_filename }} ({{ item.names | join(', ') }})
</option>
{% endfor %}
</select>
</label>
HTMX OOB contract: response body contains the primary swap fragment (targets #driver-list) PLUS one or more sibling elements with hx-swap-oob="true" whose id matches an element in the current page DOM. Out-of-band elements MUST be top-level in the response body (not nested inside the primary fragment).
Run the tests. At least `test_upload_500_regression` MAY pass or fail depending on synthetic fixture vs. real-world root cause — if it still passes with a synthetic ZIP, ALSO add a parametrized variant that feeds a ZIP containing an INF with a BOM + Windows-1252 encoded `[Strings]` section (the most likely real-world repro per 09-RESEARCH.md pitfall 1). At least one variant MUST go red before proceeding to Task 2.
The other three OOB tests MUST go red — the current handler has no `caller` support and no OOB template.
Commit: `test(09-01): add failing driver upload 500 regression + OOB contract tests`
Step 2 — Add `caller: str = Form("")` parameter to `upload_driver(request, file, caller="")` (import `Form` from fastapi). FastAPI handles mixed multipart `UploadFile` + `Form` fields natively.
Step 3 — After the existing success path builds `driver_data`, capture `new_driver` from the `get_or_create` return tuple: `new_driver, _created = Driver.get_or_create(...)`. Currently the code discards this — fix it.
Step 4 — Branch on `caller`:
```python
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},
)
# else: existing behavior unchanged
return templates.TemplateResponse(
request=request,
name="partials/driver_list.html",
context={"driver_data": driver_data, "parsed": parsed},
)
```
Step 5 — Create `imptune/templates/partials/driver_upload_with_oob.html`:
```jinja
{% include "partials/driver_list.html" %}
<select name="driver_id" id="printer-form-driver-select" hx-swap-oob="true">
<option value="">-- No driver --</option>
{% for item in driver_data %}
<option value="{{ item.driver.id }}"
{% if item.driver.id == new_driver_id %}selected{% endif %}>
{{ item.driver.original_filename }} ({{ item.names | join(', ') }})
</option>
{% endfor %}
</select>
```
Step 6 — Inspect `partials/driver_list.html`. Confirm its root element has `id="driver-list"` (hx-target from the inline upload form will swap it). If the partial currently wraps itself differently, leave as-is; the OOB template simply includes it. Do NOT refactor driver_list.html unless necessary.
Step 7 — Run the failing tests. Iterate until GREEN. Then run full non-e2e suite.
Commit: `fix(09-01): resolve driver upload 500 and add HTMX OOB refresh path`
Step 2 — Extend `tests/test_printer_form.py` with `test_printer_form_has_inline_driver_upload`:
- GET the printer form route (`/printers/new` or the HTMX partial route used by the existing tests — match the existing pattern in this test file).
- Assert response body contains `id="printer-form-driver-select"`.
- Assert response body contains `name="caller"` with value `printer_form`.
- Assert response body contains `hx-post="/drivers/upload"`.
- Assert nested form check: the substring between `<form hx-post="/printers"` and its matching `</form>` does NOT contain `hx-post="/drivers/upload"` (naive check is fine: split on `</form>` and verify the printer form chunk is clean).
Step 3 — Run the test. GREEN.
Commit: `feat(09-01): add inline driver upload to printer form with OOB refresh`
<success_criteria>
- UX-01 observable truth #1 achieved: technician uploading a driver on the printer form sees new DriverDesc in the dropdown and auto-selected, no reload
- No HTTP 500 from
POST /drivers/uploadfor the captured repro case - All 4 new tests (500 regression, OOB contract, auto-select, no-oob-on-standalone) green </success_criteria>