From 5476160417b86ff7375832c1757bce52055830d8 Mon Sep 17 00:00:00 2001 From: Kawa Date: Mon, 13 Apr 2026 10:16:54 +0200 Subject: [PATCH] docs(09): capture phase context --- .planning/STATE.md | 10 +- .../09-ux-tech-debt-closure/09-CONTEXT.md | 117 ++++++++++++++++++ 2 files changed, 122 insertions(+), 5 deletions(-) create mode 100644 .planning/phases/09-ux-tech-debt-closure/09-CONTEXT.md diff --git a/.planning/STATE.md b/.planning/STATE.md index 70b270c..7c92f72 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,8 +3,8 @@ gsd_state_version: 1.0 milestone: v1.1 milestone_name: Hardening & Validation status: Pending (roadmap approved, plans not yet drafted) -stopped_at: Phase 8 context gathered -last_updated: "2026-04-13T08:02:01.226Z" +stopped_at: Phase 9 context gathered +last_updated: "2026-04-13T08:16:54.191Z" last_activity: 2026-04-13 — v1.1 roadmap created, 15/15 requirements mapped across Phases 8–11 progress: total_phases: 4 @@ -68,6 +68,6 @@ Full decision log in PROJECT.md Key Decisions table. Milestone v1.0 decisions ar ## Session Continuity -Last session: 2026-04-13T08:02:01.223Z -Stopped at: Phase 8 context gathered -Resume file: .planning/phases/08-nyquist-validation-track/08-CONTEXT.md +Last session: 2026-04-13T08:16:54.185Z +Stopped at: Phase 9 context gathered +Resume file: .planning/phases/09-ux-tech-debt-closure/09-CONTEXT.md diff --git a/.planning/phases/09-ux-tech-debt-closure/09-CONTEXT.md b/.planning/phases/09-ux-tech-debt-closure/09-CONTEXT.md new file mode 100644 index 0000000..4cc7205 --- /dev/null +++ b/.planning/phases/09-ux-tech-debt-closure/09-CONTEXT.md @@ -0,0 +1,117 @@ +# 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 ``. +- **Refresh mechanism:** HTMX Out-of-Band (OOB) swap. `POST /drivers/upload` response is extended so that when called from the printer form (detected via `HX-Target` header or a posted sentinel field), it returns BOTH the existing `#driver-list` fragment AND an OOB-swap fragment replacing the printer form's driver ``. Saves a click in the common case ("I'm uploading this driver *for this printer*"). No confirmation prompt; keep it frictionless. +- **Existing selection preservation:** Not applicable — if the technician was mid-form and had already picked a different driver, auto-select overrides it. This is the user-requested behavior. + +### UX-01: 500 bug fix +- **Reported:** 2026-04-13 during Phase 8 kickoff. Repro: upload a driver from the `/drivers` page, server returns HTTP 500. +- **Blocker status:** Confirmed blocker for UX-01 regardless of which page triggers upload — any 500 on the upload path fails Phase 9 acceptance. Non-optional. +- **TDD flow (mandatory):** (1) Reproduce against the running app, capture the traceback. (2) Write a failing `pytest` case in `tests/test_driver_upload.py` matching the repro. (3) Confirm red. (4) Fix the root cause in `imptune/api/drivers.py` (or deeper — INF parser, DriverStore, Peewee layer). (5) Confirm green. (6) Commit the failing test and the fix atomically per GSD conventions. +- **Error-handling scope:** Fix the specific root cause of THIS 500. Do NOT rewrite the handler to swallow all exceptions into 400 responses — that would mask future bugs. Existing `_error_response()` helper returns 400 for validated failures; unhandled exceptions should remain loud but the reported repro must not be one of them. + +### UX-02: PRNT-03 live verification +- **Mechanism:** **Playwright headless test**, committed to `tests/`. Not a manual screenshot. +- **Why:** Permanent regression guard costs one-time setup, then self-maintains. A screenshot decays the moment the template changes; a Playwright test fails loudly in CI. +- **Scope of the test:** Load `/printers/new` (or equivalent printer-form route), type an IP into `input[name="ip_address"]`, assert `input[name="port_name"]` now contains `IP_` matching the Alpine.js handler in `printer_form.html:12-17`. +- **Evidence for VALIDATION.md:** The Playwright test file path + pytest command. The recorded-green test run at the commit that closes UX-02 is the evidence. No screenshot needed in the validation record — the test IS the record. +- **Dev dependency addition:** Playwright is a new dev dep. Add to `requirements-dev.txt`. No Playwright in production image (headless browsers violate the "single container, minimal deps" constraint). Test-only. + +### UX-03: Per-script download links +- **Route shape:** **Three distinct GET routes**, one per script kind. + - `GET /printers/{id}/scripts/install.ps1` + - `GET /printers/{id}/scripts/uninstall.ps1` + - `GET /printers/{id}/scripts/detect.ps1` +- **Why 3 routes over `?kind=` param:** Discoverable URLs (a technician can share `/printers/42/scripts/install.ps1` directly), trivial to bookmark, maps naturally to `Content-Disposition: attachment; filename=install.ps1`. Route sprawl is minimal (3 lines in the router). +- **Content generation:** Reuse the existing script generators (`imptune/services/` — the ones that feed package export). Plain-string generator contract, no new ORM access pattern needed. +- **Response headers:** `Content-Type: text/plain; charset=utf-8`, `Content-Disposition: attachment; filename=".ps1"`. Download, not inline-view (avoids browsers rendering `.ps1` as text and copy-paste losing CRLFs). +- **Template placement:** In [imptune/templates/printer_detail.html:48-51](imptune/templates/printer_detail.html#L48-L51), add a new `

Scripts

` block above or below the existing Export section with 3 `` links. Existing package export buttons stay untouched (UX-03 says "in addition to the existing package export buttons"). + +### Claude's Discretion +- Exact Alpine.js / HTMX OOB wiring syntax for the driver refresh. +- Whether to inline the upload control inside the printer form or stack it above — visual judgment during implementation. +- Playwright config file location and browser choice (chromium is the default, fine). +- Exact wording of the 3 download link labels ("Install script", "Install (.ps1)", etc.). +- Whether the 3 new script routes live in `imptune/api/printers.py` or a new `imptune/api/scripts.py` — router organization call. +- Test fixture format for the 500 repro (real driver ZIP vs. synthetic ZIP) — whichever reproduces fastest. + +
+ + +## 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 ``) 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 `` 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*