docs(11): create phase plan
Plan 4 plans across 3 waves covering UIE-01..05: form separation (Plan 01), printer edit modal (Plan 02), theme+i18n toggles (Plan 03), and client detail page (Plan 04). Wave 0 test scaffolds included in Plan 01. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
---
|
||||
phase: 11-ui-enhancements
|
||||
plan: "01"
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- tests/test_printer_crud.py
|
||||
- imptune/api/pages.py
|
||||
- imptune/api/printers.py
|
||||
- imptune/templates/printers.html
|
||||
- imptune/templates/printers_new.html
|
||||
autonomous: true
|
||||
requirements:
|
||||
- UIE-02
|
||||
must_haves:
|
||||
truths:
|
||||
- "GET /printers/new returns 200 with the Add Printer form"
|
||||
- "POST /printers returns 303 redirect to /printers (no HX-Request header)"
|
||||
- "GET /printers no longer contains the add-printer form markup"
|
||||
- "An 'Add Printer' link on /printers navigates to /printers/new"
|
||||
- "Existing CRUD tests still pass after the redirect behavior change"
|
||||
artifacts:
|
||||
- path: "imptune/templates/printers_new.html"
|
||||
provides: "Dedicated Add Printer page (GET /printers/new)"
|
||||
min_lines: 15
|
||||
- path: "imptune/templates/printers.html"
|
||||
provides: "Printer Library page — list only, no inline form"
|
||||
contains: "/printers/new"
|
||||
- path: "imptune/api/pages.py"
|
||||
provides: "GET /printers/new route"
|
||||
exports: ["printers_new_page"]
|
||||
- path: "imptune/api/printers.py"
|
||||
provides: "POST /printers always returns 303 redirect"
|
||||
contains: "RedirectResponse"
|
||||
key_links:
|
||||
- from: "imptune/templates/printers_new.html"
|
||||
to: "POST /printers"
|
||||
via: "plain <form> (no hx-post) so browser follows 303"
|
||||
pattern: "action=\"/printers\""
|
||||
- from: "imptune/api/printers.py"
|
||||
to: "/printers"
|
||||
via: "RedirectResponse(url='/printers', status_code=303)"
|
||||
pattern: "RedirectResponse"
|
||||
---
|
||||
|
||||
<objective>
|
||||
Separate the Add Printer form from the Printer Library and write Wave 0 test scaffolds for the whole phase.
|
||||
|
||||
Purpose: UIE-02 — Users need to add a printer on a dedicated page, not buried inside the printer list. The printer library at /printers becomes list-only with a visible "Add Printer" link.
|
||||
|
||||
Output: GET /printers/new page, updated POST /printers (303 redirect), stripped printers.html, and all integration test scaffolds for UIE-01/02/03.
|
||||
</objective>
|
||||
|
||||
<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>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/11-ui-enhancements/11-CONTEXT.md
|
||||
@.planning/phases/11-ui-enhancements/11-RESEARCH.md
|
||||
@.planning/phases/11-ui-enhancements/11-VALIDATION.md
|
||||
|
||||
<interfaces>
|
||||
<!-- Key patterns the executor needs. Extracted from live codebase. -->
|
||||
|
||||
From imptune/api/pages.py:
|
||||
```python
|
||||
# Existing printers_page route (to be modified)
|
||||
@router.get("/printers", response_class=HTMLResponse)
|
||||
def printers_page(request: Request):
|
||||
# ... loads grouped, clients, driver_data ...
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="printers.html",
|
||||
context={"grouped": grouped, "clients": clients, "driver_data": driver_data},
|
||||
)
|
||||
```
|
||||
|
||||
From imptune/api/printers.py:
|
||||
```python
|
||||
# Existing POST handler (to be changed to always redirect)
|
||||
@router.post("", response_class=HTMLResponse)
|
||||
def create_printer(request: Request, ...) -> HTMLResponse:
|
||||
# ... validate + create ...
|
||||
return _render_printer_list(request) # CHANGE: return RedirectResponse instead
|
||||
```
|
||||
|
||||
From imptune/templates/printers.html (current):
|
||||
```html
|
||||
{% block content %}
|
||||
<h1>Printers</h1>
|
||||
<section>
|
||||
<h2>Add Printer</h2>
|
||||
{% include "partials/printer_form.html" %}
|
||||
</section>
|
||||
<section>
|
||||
<h2>Printer Library</h2>
|
||||
{% include "partials/printer_list.html" %}
|
||||
</section>
|
||||
{% endblock %}
|
||||
```
|
||||
|
||||
From tests/conftest.py:
|
||||
```python
|
||||
# TestClient fixture — no follow_redirects by default (httpx default is True in TestClient)
|
||||
# Use client.post(...) and check response.status_code == 303 for redirect tests
|
||||
# Use follow_redirects=False in specific tests via: client.post(..., follow_redirects=False)
|
||||
```
|
||||
</interfaces>
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 1: Wave 0 — Add integration test scaffolds for UIE-01, UIE-02, UIE-03</name>
|
||||
<files>tests/test_printer_crud.py</files>
|
||||
<behavior>
|
||||
- test_printers_new_returns_200: GET /printers/new returns 200 with add form markup (contains 'Printer Name' or name="name")
|
||||
- test_create_printer_redirects: POST /printers (no HX-Request) returns 303 to /printers (follow_redirects=False)
|
||||
- test_printers_library_no_form: GET /printers does NOT contain the add-printer form (does not contain hx-post="/printers" or the form's submit button text "Save Printer")
|
||||
- test_patch_printer: PATCH /printers/{id} with updated name returns 200, updated name appears in response HTML, DB record updated
|
||||
- test_patch_printer_not_found: PATCH /printers/9999 returns 404
|
||||
- test_client_detail_returns_200: GET /clients/{id} (after creating client + printer assigned to it) returns 200 with client name and printer name in HTML
|
||||
- test_client_detail_not_found: GET /clients/9999 returns 404
|
||||
- test_client_links_in_printer_list: GET /printers with a printer assigned to a client contains href="/clients/{client_id}" in the response HTML
|
||||
All RED: these tests must FAIL before Plan 01 Task 2 implements the changes (except existing tests which must stay GREEN)
|
||||
</behavior>
|
||||
<action>
|
||||
Add the following test functions to the END of tests/test_printer_crud.py. Do not modify existing tests.
|
||||
|
||||
Import notes: no new imports needed beyond what is already imported (pytest, TestClient, Client, Printer from imptune.db.models are all available via conftest).
|
||||
|
||||
For test_create_printer_redirects: call client.post("/printers", data={...}, follow_redirects=False) and assert resp.status_code == 303 and resp.headers["location"] == "/printers".
|
||||
|
||||
For test_patch_printer: create a Printer directly via Printer.create(), then call client.patch(f"/printers/{printer.id}", data={...}) with an updated name, assert 200, assert updated name in resp.text, re-query DB to confirm Printer.get_by_id(printer.id).name == updated name.
|
||||
|
||||
For test_client_links_in_printer_list: create a client via POST /clients, create a printer assigned to that client via Printer.create(), GET /printers, assert f'href="/clients/{client.id}"' in resp.text.
|
||||
|
||||
Existing tests that POST to /printers (e.g. test_create_printer_persisted) will break after Task 2 changes the POST handler. Add a FIXME comment above each existing POST test noting they will be updated in Task 2, but do NOT change them yet — let them go RED as part of TDD RED state.
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && pytest tests/test_printer_crud.py -x -q -k "printers_new or redirects or library_no_form or patch_printer or client_detail or client_not_found or client_links" 2>&1 | tail -10</automated>
|
||||
Expected: all new tests FAIL (RED state — routes/templates do not exist yet).
|
||||
</verify>
|
||||
<done>New test functions exist in test_printer_crud.py; running them against current code produces FAIL/ERROR (not ImportError); existing passing tests still pass when run without the new tests.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto" tdd="true">
|
||||
<name>Task 2: UIE-02 — Separate form from library (GET /printers/new + POST redirect)</name>
|
||||
<files>
|
||||
imptune/api/pages.py,
|
||||
imptune/api/printers.py,
|
||||
imptune/templates/printers.html,
|
||||
imptune/templates/printers_new.html
|
||||
</files>
|
||||
<behavior>
|
||||
- GET /printers returns 200; response does NOT contain the printer form (no "Save Printer" button, no hx-post="/printers")
|
||||
- GET /printers contains an "Add Printer" link/button pointing to /printers/new
|
||||
- GET /printers/new returns 200 and contains the printer form (contains name="name", name="ip_address")
|
||||
- POST /printers (plain form, no HX-Request header) returns 303 redirect to /printers
|
||||
- After redirect, GET /printers shows the newly created printer in the list
|
||||
</behavior>
|
||||
<action>
|
||||
**Step 1 — Create imptune/templates/printers_new.html:**
|
||||
New full page template extending base.html. Block content contains:
|
||||
- <h1> heading (e.g. "Add Printer" — use x-text="$store.i18n.t('add_printer')" when UIE-05 lands; for now, hardcode "Add Printer")
|
||||
- <a href="/printers"> back link
|
||||
- {% include "partials/printer_form.html" %} — reuse the existing partial unchanged; it already has all fields and the driver upload sub-form
|
||||
Context vars needed: clients (list of Client), driver_data (list of {driver, names})
|
||||
|
||||
**Step 2 — Update imptune/templates/printers.html:**
|
||||
Remove the entire "Add Printer" <section> block (the {% include "partials/printer_form.html" %} section).
|
||||
Replace it with a prominent "Add Printer" link styled as a button: <a href="/printers/new" role="button">Add Printer</a>
|
||||
Keep the "Printer Library" section with {% include "partials/printer_list.html" %} unchanged.
|
||||
|
||||
**Step 3 — Update imptune/api/pages.py:**
|
||||
a) Modify printers_page (GET /printers): remove the driver_data context since the form is no longer there. Keep grouped and clients for the list rendering and modal (Plan 02 will need them). Actually keep driver_data — it will be needed by the edit modal in Plan 02. Leave context unchanged.
|
||||
b) Add new route GET /printers/new:
|
||||
```python
|
||||
@router.get("/printers/new", response_class=HTMLResponse)
|
||||
def printers_new_page(request: Request):
|
||||
from imptune.db.models import Client, Driver
|
||||
import json
|
||||
clients = list(Client.select().order_by(Client.name))
|
||||
all_drivers = list(Driver.select().order_by(Driver.uploaded_at.desc()))
|
||||
driver_data = [
|
||||
{"driver": d, "names": json.loads(d.driver_desc) if d.driver_desc else []}
|
||||
for d in all_drivers
|
||||
]
|
||||
return templates.TemplateResponse(
|
||||
request=request,
|
||||
name="printers_new.html",
|
||||
context={"clients": clients, "driver_data": driver_data},
|
||||
)
|
||||
```
|
||||
IMPORTANT: Place this route BEFORE the GET /printers/{printer_id} route in pages.py to avoid FastAPI routing the literal string "new" as a printer_id int (FastAPI path parameter typing already handles this since printer_id is typed int, but explicit ordering avoids ambiguity).
|
||||
|
||||
**Step 4 — Update imptune/api/printers.py:**
|
||||
Change the POST /printers handler to always return a RedirectResponse:
|
||||
```python
|
||||
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||
# ... after Printer.create() succeeds ...
|
||||
return RedirectResponse(url="/printers", status_code=303)
|
||||
```
|
||||
Remove the `return _render_printer_list(request)` line at the end of create_printer. The _render_printer_list helper stays (used by DELETE and future PATCH in Plan 02).
|
||||
|
||||
**Step 5 — Fix existing tests broken by redirect:**
|
||||
Update existing tests in test_printer_crud.py that POST to /printers and previously asserted status_code == 200:
|
||||
- For tests that just test DB persistence (test_create_printer_persisted, test_create_printer_duplex, etc.): change assertion from `assert resp.status_code == 200` to `assert resp.status_code == 303`. The DB create still happens before the redirect. These tests do not need to follow the redirect.
|
||||
- For test_create_printer_persisted: after the POST, do a separate `client.get("/printers")` to verify the name appears (the existing code already does this — just update the status_code assertion for the POST itself).
|
||||
- Do NOT change the 400 error tests (test_create_printer_missing_name, test_create_printer_invalid_ip) — error responses are still returned directly (no redirect on validation failure).
|
||||
|
||||
Note: printer_form.html currently uses hx-post="/printers". Since printers_new.html will use {% include "partials/printer_form.html" %}, the form will submit via HTMX by default. Change the form action in printer_form.html to use a plain form without HTMX on /printers/new by one of two approaches:
|
||||
- Option A (preferred): In printers_new.html, do NOT include printer_form.html via {% include %}. Instead, copy the form markup inline but replace hx-post="/printers" with action="/printers" method="post" (plain HTML form). This ensures the browser follows the 303 redirect naturally.
|
||||
- The driver upload sub-form can stay as-is with hx-post (it has its own target and handler).
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && pytest tests/test_printer_crud.py -x -q -k "printers_new or redirects or library_no_form" 2>&1 | tail -15</automated>
|
||||
Also run: pytest tests/test_printer_crud.py -x -q 2>&1 | tail -10 (all tests GREEN)
|
||||
</verify>
|
||||
<done>GET /printers/new returns 200 with form; POST /printers returns 303; GET /printers contains "Add Printer" link but no form; all test_printer_crud.py tests pass.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
Run full test suite (excluding E2E) after both tasks complete:
|
||||
|
||||
```
|
||||
cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && pytest tests/ -x -q --ignore=tests/e2e
|
||||
```
|
||||
|
||||
Expected: all tests GREEN.
|
||||
|
||||
Spot-check:
|
||||
- `pytest tests/test_printer_crud.py -x -q -k "printers_new or redirects or library_no_form"` — GREEN (UIE-02 tests)
|
||||
- `pytest tests/test_printer_crud.py -x -q -k "patch_printer or client_detail or client_not_found or client_links"` — RED (UIE-01/03 tests scaffold exists but routes not yet built — expected RED at end of Plan 01)
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- GET /printers/new returns 200 with the Add Printer form (all printer fields present)
|
||||
- POST /printers returns 303 redirect to /printers (confirmed by test)
|
||||
- GET /printers does NOT contain the Add Printer form markup
|
||||
- GET /printers contains a link to /printers/new
|
||||
- All existing tests in test_printer_crud.py pass (adjusted for 303 on POST)
|
||||
- Wave 0 scaffolds for UIE-01 and UIE-03 exist in test_printer_crud.py (RED, not ERROR)
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/11-ui-enhancements/11-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user