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
This commit is contained in:
@@ -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: <div id='printer-list' class='error'><p>{msg}</p></div> 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*
|
||||
Reference in New Issue
Block a user