From 3b644272e7e5fe9e98a23852497ce7cbd36277cd Mon Sep 17 00:00:00 2001 From: Kawa Date: Fri, 10 Apr 2026 12:57:43 +0200 Subject: [PATCH] docs(03-01): complete printer and client CRUD plan summary - 03-01-SUMMARY.md: documents printer/client CRUD, Alpine.js port derivation, HTMX partials, test isolation fix, and PRNT-01 through PRNT-09 requirements - STATE.md: updated position, decisions, metrics, session record - ROADMAP.md: phase 03 progress updated (1/2 plans complete) - REQUIREMENTS.md: PRNT-01 through PRNT-09 marked complete --- .planning/REQUIREMENTS.md | 36 ++-- .planning/STATE.md | 16 +- .../03-printer-configuration/03-01-SUMMARY.md | 166 ++++++++++++++++++ 3 files changed, 194 insertions(+), 24 deletions(-) create mode 100644 .planning/phases/03-printer-configuration/03-01-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 4db464c..dc2e08c 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -17,15 +17,15 @@ Requirements for initial release. Each maps to roadmap phases. ### Printer Configuration -- [ ] **PRNT-01**: User can set printer display name -- [ ] **PRNT-02**: User can set printer IP address or hostname -- [ ] **PRNT-03**: System auto-suggests port name from IP (user can override) -- [ ] **PRNT-04**: User can set duplex mode (one-sided, long-edge, short-edge) -- [ ] **PRNT-05**: User can set color vs. grayscale default -- [ ] **PRNT-06**: User can set paper size (A4, Letter, Legal at minimum) -- [ ] **PRNT-07**: User can set collate on/off -- [ ] **PRNT-08**: User can assign printer to a client/tenant label -- [ ] **PRNT-09**: Printer configurations are persisted in SQLite across sessions +- [x] **PRNT-01**: User can set printer display name +- [x] **PRNT-02**: User can set printer IP address or hostname +- [x] **PRNT-03**: System auto-suggests port name from IP (user can override) +- [x] **PRNT-04**: User can set duplex mode (one-sided, long-edge, short-edge) +- [x] **PRNT-05**: User can set color vs. grayscale default +- [x] **PRNT-06**: User can set paper size (A4, Letter, Legal at minimum) +- [x] **PRNT-07**: User can set collate on/off +- [x] **PRNT-08**: User can assign printer to a client/tenant label +- [x] **PRNT-09**: Printer configurations are persisted in SQLite across sessions - [ ] **PRNT-10**: User can regenerate a package from saved config without re-uploading drivers ### Script Generation @@ -90,15 +90,15 @@ Which phases cover which requirements. Updated during roadmap creation. | DRV-03 | Phase 2 | Complete | | DRV-04 | Phase 2 | Complete | | DRV-05 | Phase 2 | Complete | -| PRNT-01 | Phase 3 | Pending | -| PRNT-02 | Phase 3 | Pending | -| PRNT-03 | Phase 3 | Pending | -| PRNT-04 | Phase 3 | Pending | -| PRNT-05 | Phase 3 | Pending | -| PRNT-06 | Phase 3 | Pending | -| PRNT-07 | Phase 3 | Pending | -| PRNT-08 | Phase 3 | Pending | -| PRNT-09 | Phase 3 | Pending | +| PRNT-01 | Phase 3 | Complete | +| PRNT-02 | Phase 3 | Complete | +| PRNT-03 | Phase 3 | Complete | +| PRNT-04 | Phase 3 | Complete | +| PRNT-05 | Phase 3 | Complete | +| PRNT-06 | Phase 3 | Complete | +| PRNT-07 | Phase 3 | Complete | +| PRNT-08 | Phase 3 | Complete | +| PRNT-09 | Phase 3 | Complete | | PRNT-10 | Phase 3 | Pending | | SCRPT-01 | Phase 4 | Pending | | SCRPT-02 | Phase 4 | Pending | diff --git a/.planning/STATE.md b/.planning/STATE.md index a38539c..bfc336e 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Completed 02-02-PLAN.md -last_updated: "2026-04-10T10:06:27.952Z" +stopped_at: Completed 03-01-PLAN.md — awaiting human-verify checkpoint (Task 3) +last_updated: "2026-04-10T10:57:31.851Z" last_activity: "2026-04-10 — Plan 01-01 complete: Docker scaffold, FastAPI app shell, sidebar templates, 3-test green suite" progress: total_phases: 5 completed_phases: 2 - total_plans: 5 - completed_plans: 5 + total_plans: 7 + completed_plans: 6 percent: 7 --- @@ -53,6 +53,7 @@ Progress: [░░░░░░░░░░] 7% | Phase 01-foundation P01-02 | 3 | 2 tasks | 7 files | | Phase 02 P01 | 3 | 1 tasks | 6 files | | Phase 02-driver-management P02-02 | 3 | 2 tasks | 7 files | +| Phase 03-printer-configuration P01 | 7 | 2 tasks | 12 files | ## Accumulated Context @@ -76,6 +77,9 @@ Recent decisions affecting current work: - [Phase 02-driver-management]: Dynamic DRIVERS_DIR read (import config module not constant) so monkeypatch works in integration tests - [Phase 02-driver-management]: TestClient must use context manager for Starlette 0.46+ lifespan trigger (init_db) - [Phase 02-driver-management]: HTMX errors: return HTMLResponse with driver-list div wrapper so HTMX outerHTML swap works for error states +- [Phase 03-printer-configuration]: Use list(Model.select().where()) in tests instead of Model.get() — Peewee get() cursor caching fails across DB re-inits in same process +- [Phase 03-printer-configuration]: Close test-thread DB connection in conftest.py teardown — thread-local Peewee connections persist across tests and read from stale DB +- [Phase 03-printer-configuration]: Alpine.js portEdited guard prevents port overwrite after manual edit (PRNT-03) ### Pending Todos @@ -88,6 +92,6 @@ None yet. ## Session Continuity -Last session: 2026-04-10T10:03:29.685Z -Stopped at: Completed 02-02-PLAN.md +Last session: 2026-04-10T10:57:31.848Z +Stopped at: Completed 03-01-PLAN.md — awaiting human-verify checkpoint (Task 3) Resume file: None diff --git a/.planning/phases/03-printer-configuration/03-01-SUMMARY.md b/.planning/phases/03-printer-configuration/03-01-SUMMARY.md new file mode 100644 index 0000000..7bc99b6 --- /dev/null +++ b/.planning/phases/03-printer-configuration/03-01-SUMMARY.md @@ -0,0 +1,166 @@ +--- +phase: 03-printer-configuration +plan: "01" +subsystem: api +tags: [fastapi, peewee, htmx, alpinejs, jinja2, sqlite, forms] + +requires: + - phase: 02-driver-management + provides: Driver ORM model, HTMX partial rendering pattern, error response pattern, test fixtures with tmp_data_dir + +provides: + - POST /printers endpoint with form parsing, validation, checkbox-to-bool conversion, FK resolution + - DELETE /printers/{id} endpoint + - POST /clients endpoint with duplicate-name handling + - GET /printers page grouped by client with LEFT OUTER JOIN (no N+1) + - GET /clients page with creation form + - Alpine.js port auto-derivation (IP -> port name, preserves manual edits) + - HTMX-powered form submission with outerHTML swap on #printer-list and #client-list + - Integration test suite covering PRNT-01 through PRNT-09 + +affects: + - 03-02 (next plan in printer configuration phase) + - Any phase using Printer or Client ORM models + - Test isolation pattern now fixed in conftest.py (affects all future test suites) + +tech-stack: + added: [] + patterns: + - "Printer/Client CRUD via FastAPI Form() parameters with sync def handlers" + - "Checkbox boolean convention: 'on'=True, absent/empty=False" + - "Grouped list via defaultdict + LEFT_OUTER JOIN — no N+1 queries" + - "HTMX partial swap: success returns partial, failure returns error div with same id" + - "Alpine.js x-data for reactive port derivation with portEdited guard" + - "Peewee test isolation: conftest.py fixture teardown closes test-thread DB connection" + +key-files: + created: + - imptune/api/printers.py + - imptune/api/clients.py + - imptune/templates/printers.html + - imptune/templates/clients.html + - imptune/templates/partials/printer_form.html + - imptune/templates/partials/printer_list.html + - imptune/templates/partials/client_list.html + - tests/test_printer_crud.py + modified: + - imptune/api/pages.py + - imptune/main.py + - imptune/db/database.py + - tests/conftest.py + +key-decisions: + - "Use list(Printer.select().where(...)) in tests instead of Printer.get() — Peewee's get() uses paginate+cursor caching that fails across DB re-inits in the same process" + - "Close test-thread DB connection in conftest.py fixture teardown — thread-local Peewee connections persist across tests and read from stale DB" + - "Close db in lifespan shutdown — enables clean re-init when TestClient is restarted in the same process" + - "Alpine.js portEdited guard prevents port overwrite after manual edit (PRNT-03 requirement)" + +patterns-established: + - "HTMX error fragment:

{msg}

with matching id for outerHTML swap" + - "Grouped list query: LEFT_OUTER JOIN with defaultdict grouping, 'Unassigned' fallback for null FK" + - "Form checkbox handling: Form('') default, 'on' == True conversion" + +requirements-completed: + - PRNT-01 + - PRNT-02 + - PRNT-03 + - PRNT-04 + - PRNT-05 + - PRNT-06 + - PRNT-07 + - PRNT-08 + - PRNT-09 + +duration: 7min +completed: "2026-04-10" +--- + +# Phase 03 Plan 01: Printer and Client CRUD Summary + +**FastAPI printer CRUD with Alpine.js IP-to-port derivation, HTMX form submission, LEFT JOIN grouped list by client, and 10-test integration suite covering PRNT-01 through PRNT-09** + +## Performance + +- **Duration:** ~7 min +- **Started:** 2026-04-10T10:49:28Z +- **Completed:** 2026-04-10T10:56:22Z +- **Tasks:** 2 of 3 (Task 3 is checkpoint:human-verify — pending) +- **Files modified:** 12 + +## Accomplishments + +- Printer CRUD: POST /printers (all 9 fields, checkbox bool conversion, optional FK), DELETE /printers/{id} +- Client CRUD: POST /clients (duplicate handling), GET /clients page +- Alpine.js port auto-derivation: fills `IP_x_x_x_x` from IP, preserves manual edits via `portEdited` guard +- Grouped list: LEFT_OUTER JOIN query, defaultdict grouping with "Unassigned" fallback, no N+1 +- 10 integration tests pass (GREEN), full 58-test suite passes + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Failing integration tests (RED)** - `9bc26e3` (test) +2. **Task 2: Full CRUD implementation + GREEN tests** - `356c2ee` (feat) +3. **Task 3: Browser verification** - pending (checkpoint:human-verify) + +## Files Created/Modified + +- `imptune/api/printers.py` - POST /printers, DELETE /printers/{id}, _render_printer_list helper +- `imptune/api/clients.py` - POST /clients, _render_client_list helper +- `imptune/api/pages.py` - Added GET /printers and GET /clients page routes +- `imptune/main.py` - Registered printers + clients routers; db.close() in lifespan shutdown +- `imptune/db/database.py` - Close existing connection before re-init in init_db() +- `imptune/templates/printers.html` - Printer page (form + list sections) +- `imptune/templates/clients.html` - Clients page (add form + list) +- `imptune/templates/partials/printer_form.html` - All 9 fields, Alpine.js x-data reactivity +- `imptune/templates/partials/printer_list.html` - Grouped by client with h3 headers, delete buttons +- `imptune/templates/partials/client_list.html` - Client table partial +- `tests/conftest.py` - Added db.close() teardown in tmp_data_dir fixture +- `tests/test_printer_crud.py` - 10 integration tests for all PRNT requirements + +## Decisions Made + +- Use `list(Model.select().where(...))` in tests instead of `Model.get()` — Peewee's `get()` uses `paginate(1,1)` with cursor caching that hits the wrong database when the deferred db is re-initialized between tests in the same process. +- Close db connection in conftest.py fixture teardown — thread-local Peewee connections persist across tests and read from stale DB path even after `db.init()` updates the path. +- Alpine.js `portEdited` boolean guard preserves manually edited port names when user changes IP (PRNT-03). + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Peewee thread-local DB connection leaks across test boundaries** +- **Found during:** Task 2 (GREEN phase verification) +- **Issue:** After TestClient exits and a new test begins with a fresh tmp DB, the test thread's Peewee connection still pointed at the previous test's DB file. `Printer.get()` would query the wrong database (empty or stale data). +- **Fix:** + 1. Added `db.close()` in lifespan shutdown (main.py) so each TestClient teardown closes the ASGI-thread connection. + 2. Added `if not db.is_closed(): db.close()` before `db.init()` in `init_db()` (database.py) so re-init always starts fresh. + 3. Added db connection teardown in `conftest.py` `tmp_data_dir` fixture to close the test-thread's connection after each test. + 4. Updated test DB queries from `Model.get()` to `list(Model.select().where(...))` to avoid Peewee paginate cursor caching issue. +- **Files modified:** imptune/main.py, imptune/db/database.py, tests/conftest.py, tests/test_printer_crud.py +- **Verification:** All 58 tests pass including cross-test ordering +- **Committed in:** `356c2ee` (Task 2 commit) + +--- + +**Total deviations:** 1 auto-fixed (Rule 1 - Bug) +**Impact on plan:** Fix was necessary for test correctness. The underlying isolation pattern now benefits all future test suites in this project. No scope creep. + +## Issues Encountered + +- Peewee `Model.get()` uses `paginate(1,1)` which clears `_cursor_wrapper` cache and re-executes — but after db re-init, the cursor wrapper was returning empty even though `count()` and direct SQL showed the record existed. Root cause: thread-local SQLite connection not updated by `db.init()`. Resolved by proper connection lifecycle management. + +## User Setup Required + +None — no external service configuration required. + +## Next Phase Readiness + +- /printers and /clients pages functional with full CRUD +- Alpine.js port auto-derivation implemented (PRNT-03) — browser verification still pending (Task 3 checkpoint) +- Printer form supports driver dropdown from uploaded drivers +- Grouped printer list ready for 03-02 (script generation) +- Test isolation pattern fixed — future test suites can safely use `list(Model.select().where(...))` for DB assertions + +--- +*Phase: 03-printer-configuration* +*Completed: 2026-04-10*