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>
11 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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11-ui-enhancements | 04 | execute | 3 |
|
|
true |
|
|
Purpose: UIE-03 — Users managing multiple clients need a quick way to see only one client's printers. A clickable client name in the list/headers navigates to a filtered view without any extra search UI.
Output: GET /clients/{client_id} route, client_detail.html template, updated client_list.html and printer_list.html with client name links.
<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/phases/11-ui-enhancements/11-CONTEXT.md @.planning/phases/11-ui-enhancements/11-RESEARCH.md @.planning/phases/11-ui-enhancements/11-02-SUMMARY.md @.planning/phases/11-ui-enhancements/11-03-SUMMARY.mdFrom imptune/api/pages.py — existing clients_page route (for reference):
@router.get("/clients", response_class=HTMLResponse)
def clients_page(request: Request):
from imptune.db.models import Client
clients = list(Client.select().order_by(Client.name))
return templates.TemplateResponse(
request=request,
name="clients.html",
context={"clients": clients},
)
New route to add:
@router.get("/clients/{client_id}", response_class=HTMLResponse)
def client_detail(request: Request, client_id: int):
# Query client, 404 if not found
# Query printers filtered by client_id
# grouped = {client.name: list(query)} -- single-key dict for printer_list.html reuse
# driver_data for modal
# clients list for modal client dropdown
...
From imptune/templates/partials/printer_list.html — current group header (to update):
{% for client_name, printers in grouped.items() %}
<section>
<h3>{{ client_name }}</h3>
...
Update to:
<h3><a href="/clients/{{ client_id_map[client_name] }}">{{ client_name }}</a></h3>
OR: Pass grouped as dict of {client_id -> (client_name, printers)} — refactor the grouped structure.
RECOMMENDED APPROACH: Since printer_list.html receives grouped as {client_name: [printers]}, and p.client is available in each loop, extract the client_id from the first printer in the group: {% set client_id = (printers[0].client_id if printers) %}. This avoids changing the grouped data structure.
For "Unassigned" group: client_id will be None/empty — no link, just plain text.
From imptune/templates/partials/client_list.html — current client name cell:
<td>{{ c.name }}</td>
Update to:
<td><a href="/clients/{{ c.id }}">{{ c.name }}</a></td>
From tests/test_printer_crud.py — scaffolded tests (RED from Plan 01, now going GREEN):
- test_client_detail_returns_200
- test_client_detail_not_found
- test_client_links_in_printer_list
Add after the existing clients_page route:
```python
@router.get("/clients/{client_id}", response_class=HTMLResponse)
def client_detail(request: Request, client_id: int):
from imptune.db.models import Client, Driver, Printer
import json
client = Client.get_or_none(Client.id == client_id)
if client is None:
return HTMLResponse(
content="<h1>404 Not Found</h1><p>Client not found.</p>",
status_code=404,
)
query = (
Printer.select(Printer, Client)
.join(Client, JOIN.LEFT_OUTER)
.where(Printer.client == client_id)
.order_by(Printer.name)
)
grouped = {client.name: list(query)}
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="client_detail.html",
context={
"client": client,
"grouped": grouped,
"clients": clients,
"driver_data": driver_data,
},
)
```
**Step 2 — Create imptune/templates/client_detail.html:**
```html
{% extends "base.html" %}
{% block content %}
<h1>{{ client.name }}</h1>
<p><a href="/clients">← All Clients</a></p>
<section>
<h2>Printers</h2>
{% include "partials/printer_list.html" %}
</section>
{% endblock %}
```
This template reuses printer_list.html which already handles the Edit and Delete actions (from Plan 02). The `grouped`, `clients`, and `driver_data` context vars are all passed from the route handler so the printer list and edit modals work identically to /printers.
Change the `<h3>{{ client_name }}</h3>` to:
```html
{% set group_client_id = printers[0].client_id if printers else None %}
{% if group_client_id %}
<h3><a href="/clients/{{ group_client_id }}">{{ client_name }}</a></h3>
{% else %}
<h3>{{ client_name }}</h3>
{% endif %}
```
This extracts the client_id from the first printer in the group. For "Unassigned" (client_id=None), the condition is False and plain text is rendered.
Verify: the Jinja2 template uses `{% for client_name, printers in grouped.items() %}` — `printers` is available as the inner list, so `printers[0].client_id` is accessible. The `client_id` attribute is a Peewee FK field that returns the raw integer when accessed as `p.client_id` (not the FK object).
**Step 2 — Update imptune/templates/partials/client_list.html:**
Change:
```html
<td>{{ c.name }}</td>
```
to:
```html
<td><a href="/clients/{{ c.id }}">{{ c.name }}</a></td>
```
That's the only change needed in client_list.html.
Run full E2E suite:
cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && pytest tests/e2e/ -q
Expected: all E2E tests GREEN (port_autofill + printer_edit + theme_toggle + i18n_toggle).
Verify UIE-03 integration test coverage:
pytest tests/test_printer_crud.py -v -k "client_detail or client_not_found or client_links"
Expected: 3 tests GREEN.
<success_criteria>
- GET /clients/{id} returns 200 with client name and filtered printer list
- GET /clients/9999 returns 404
- Client names in printer list group headers link to /clients/{id}
- Client names in clients table link to /clients/{id}
- "Unassigned" group header is plain text (no dead link)
- Edit and Delete actions on /clients/{id} page work via the reused printer_list partial
- All Phase 11 integration tests GREEN (UIE-01/02/03 scaffolds fully resolved)
- Full test suite (including E2E) GREEN </success_criteria>