--- phase: 11-ui-enhancements plan: "04" type: execute wave: 3 depends_on: - "11-02" - "11-03" files_modified: - imptune/api/pages.py - imptune/templates/client_detail.html - imptune/templates/partials/client_list.html - imptune/templates/partials/printer_list.html autonomous: true requirements: - UIE-03 must_haves: truths: - "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" artifacts: - path: "imptune/templates/client_detail.html" provides: "Per-client page showing client name + filtered printer list" min_lines: 15 - path: "imptune/api/pages.py" provides: "GET /clients/{client_id} route" exports: ["client_detail"] - path: "imptune/templates/partials/client_list.html" provides: "Client names wrapped in " contains: "/clients/" - path: "imptune/templates/partials/printer_list.html" provides: "Group headers with client name as " contains: "/clients/" key_links: - from: "imptune/templates/partials/client_list.html" to: "/clients/{c.id}" via: "{{ c.name }}" pattern: "href.*clients.*c\\.id" - from: "imptune/templates/partials/printer_list.html" to: "/clients/{client_id}" via: "{{ client_name }} in group header

" pattern: "href.*clients" - from: "imptune/api/pages.py client_detail" to: "printer_list partial" via: "grouped = {client.name: list(query)} passed to client_detail.html which includes printer_list" pattern: "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. @C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md @C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md @.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): ```python @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: ```python @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): ```html {% for client_name, printers in grouped.items() %}

{{ client_name }}

... ``` Update to: ```html

{{ client_name }}

``` 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: ```html {{ c.name }} ``` Update to: ```html {{ c.name }} ``` 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="

404 Not Found

Client not found.

", 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 %}

{{ client.name }}

← All Clients

Printers

{% include "partials/printer_list.html" %}
{% 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 `

{{ client_name }}

` to: ```html {% set group_client_id = printers[0].client_id if printers else None %} {% if group_client_id %}

{{ client_name }}

{% else %}

{{ client_name }}

{% 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 {{ c.name }} ``` to: ```html {{ c.name }} ``` 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. - 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 After completion, create `.planning/phases/11-ui-enhancements/11-04-SUMMARY.md`