Files
ImpTune/.planning/phases/11-ui-enhancements/11-04-PLAN.md
T
kawaandClaude Sonnet 4.6 0badba20d1 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>
2026-04-15 10:47:19 +02:00

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
11-02
11-03
imptune/api/pages.py
imptune/templates/client_detail.html
imptune/templates/partials/client_list.html
imptune/templates/partials/printer_list.html
true
UIE-03
truths artifacts key_links
Every client name in the printer list group headers is a clickable link to /clients/{id}
Every client name in the client list table is a clickable link to /clients/{id}
GET /clients/{id} returns 200 with the client name as page title and only that client's printers listed
GET /clients/9999 returns 404
Edit and Delete actions on /clients/{id} work the same as on /printers
path provides min_lines
imptune/templates/client_detail.html Per-client page showing client name + filtered printer list 15
path provides exports
imptune/api/pages.py GET /clients/{client_id} route
client_detail
path provides contains
imptune/templates/partials/client_list.html Client names wrapped in <a href='/clients/{c.id}'> /clients/
path provides contains
imptune/templates/partials/printer_list.html Group headers with client name as <a href='/clients/{client_id}'> /clients/
from to via pattern
imptune/templates/partials/client_list.html /clients/{c.id} <a href='/clients/{{ c.id }}'>{{ c.name }}</a> href.*clients.*c.id
from to via pattern
imptune/templates/partials/printer_list.html /clients/{client_id} <a href='/clients/{id}'>{{ client_name }}</a> in group header <h3> href.*clients
from to via pattern
imptune/api/pages.py client_detail printer_list partial grouped = {client.name: list(query)} passed to client_detail.html which includes printer_list grouped
Make client names clickable everywhere they appear, and add the per-client printer page at GET /clients/{id}.

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.md

From 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
Task 1: GET /clients/{id} route + client_detail.html template imptune/api/pages.py, imptune/templates/client_detail.html - GET /clients/{id} (existing client with printers) returns 200, contains client name, contains printer names assigned to that client - GET /clients/{id} (existing client with no printers) returns 200, contains client name - GET /clients/9999 returns 404 These tests are the RED scaffolds from Plan 01 (test_client_detail_returns_200, test_client_detail_not_found) **Step 1 — Add GET /clients/{client_id} route to imptune/api/pages.py:**
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">&larr; 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.
cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && pytest tests/test_printer_crud.py -x -q -k "client_detail or client_not_found" 2>&1 | tail -10 test_client_detail_returns_200 and test_client_detail_not_found both GREEN. Task 2: Client name links in printer_list.html and client_list.html imptune/templates/partials/printer_list.html, imptune/templates/partials/client_list.html - GET /printers with a printer assigned to a client contains `href="/clients/{client_id}"` in the response HTML - Client names in the client table are wrapped in anchor tags pointing to /clients/{id} - "Unassigned" group header in printer list is plain text (no link — no client ID to link to) This test is the RED scaffold from Plan 01 (test_client_links_in_printer_list) **Step 1 — Update imptune/templates/partials/printer_list.html group headers:**
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.
cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && pytest tests/test_printer_crud.py -x -q -k "client_links" 2>&1 | tail -10 Also full suite: pytest tests/ -x -q --ignore=tests/e2e - test_client_links_in_printer_list GREEN - Client names in client_list.html are wrapped in anchor tags - Group headers in printer_list.html link to /clients/{id} for assigned clients - "Unassigned" group header remains plain text - All non-E2E tests GREEN Run full non-E2E suite: ``` cd C:/Users/SebastienQUEROL/Documents/projets/ImpTune && pytest tests/ -x -q --ignore=tests/e2e ``` Expected: all GREEN — all UIE-01/02/03 scaffolded tests now GREEN.

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>
After completion, create `.planning/phases/11-ui-enhancements/11-04-SUMMARY.md`