diff --git a/.planning/phases/11-ui-enhancements/11-RESEARCH.md b/.planning/phases/11-ui-enhancements/11-RESEARCH.md
new file mode 100644
index 0000000..dcc452b
--- /dev/null
+++ b/.planning/phases/11-ui-enhancements/11-RESEARCH.md
@@ -0,0 +1,568 @@
+# Phase 11: UI Enhancements - Research
+
+**Researched:** 2026-04-15
+**Domain:** FastAPI + Jinja2 + HTMX + Alpine.js + Pico CSS — frontend UX improvements
+**Confidence:** HIGH (all findings grounded in live codebase inspection)
+
+---
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+
+**UIE-01 — Printer Edit Modal:**
+- Edit button added to each row in the Actions column, next to the existing Delete button (small button, labelled "Edit")
+- Clicking Edit opens a native HTML `
+
+---
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|-----------------|
+| UIE-01 | Printer edit modal via native ``, pre-filled, HTMX PATCH `/printers/{id}`, in-place list refresh | PATCH route pattern, dialog open/close, reuse printer_form partial minus upload form |
+| UIE-02 | Separate `/printers/new` page for add form; `/printers` shows library + "Add Printer" link; POST redirects back | New GET route `/printers/new` in pages.py, redirect on POST, `/printers` strips form |
+| UIE-03 | Client names as links to `/clients/{id}`; per-client page with filtered printer list | New GET route `/clients/{id}` in pages.py, filtered Printer query, client_id lookup |
+| UIE-04 | Theme toggle (Light/Dark/System) in top-right; Alpine.js sets `data-theme` on ``; persisted in localStorage | Pico CSS 2.1.1 native data-theme support confirmed, Alpine.js store pattern |
+| UIE-05 | FR/EN language toggle; Alpine.js `$store` with translation dictionary; instant update, localStorage persist | Alpine.js `$store` global reactive store, x-text binding pattern |
+
+
+---
+
+## Summary
+
+Phase 11 is a pure frontend sprint on top of a fully operational v1.0 backend. No database schema changes, no new backend business logic. The stack is locked: **FastAPI 0.115, Jinja2 3.1, HTMX 2.0.8, Alpine.js 3.15.11, Pico CSS 2.1.1** — all bundled statically, no CDN dependency, no npm build step.
+
+The five requirements decompose into three backend touches (one new PATCH route, two new GET page routes) and two purely frontend changes (theme toggle + i18n store, both live entirely in `base.html`). The heaviest integration work is UIE-01 (edit modal): it reuses the existing `printer_form.html` partial with a changed form action and HTMX method, wrapped in a `` element that Pico CSS already styles natively.
+
+The test infrastructure is pytest (unit/integration via `TestClient`) plus pytest-playwright for E2E. All new routes have clear HTTP-level contracts testable without a browser. Alpine.js-driven behaviours (theme persistence, language switching) require either E2E or visual-manual verification; unit tests cannot observe DOM state mutations from `x-data`.
+
+**Primary recommendation:** Implement in 3 plans: (1) backend routes + modal PATCH + form separation, (2) client detail page, (3) base.html theme + i18n controls.
+
+---
+
+## Standard Stack
+
+### Core (all versions confirmed from static files and requirements.txt)
+
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| FastAPI | 0.115.x | HTTP routing, form parsing, template rendering | Already the app framework |
+| Jinja2 | 3.1.x | Server-side HTML templating with `{% include %}` | Already used for all pages and partials |
+| HTMX | 2.0.8 | Declarative AJAX — POST/DELETE/PATCH with HTML swap | Already used for all list mutations |
+| Alpine.js | 3.15.11 | Client-side reactivity, `x-data`, `$store` | Already used for port auto-fill; `defer` loaded |
+| Pico CSS | 2.1.1 | Semantic CSS framework with `` + `data-theme` support | Already loaded; native dialog + theme support confirmed |
+| Peewee | 3.17.x | ORM for SQLite — Printer/Client/Driver models | Already the ORM; no schema changes needed |
+| pytest + TestClient | 8.x / httpx 0.27 | HTTP-level integration tests | Existing test harness used across 14 plans |
+| pytest-playwright | current | E2E Alpine.js verification | Established in Phase 9 UX-02 |
+
+### No New Dependencies
+This phase requires zero new pip installs and zero new JS libraries. Everything needed is already bundled in `imptune/static/`.
+
+---
+
+## Architecture Patterns
+
+### Recommended Project Structure Changes
+
+```
+imptune/
+├── api/
+│ ├── printers.py # ADD: PATCH /printers/{id} handler
+│ └── clients.py # (no changes needed — POST /clients unchanged)
+├── templates/
+│ ├── base.html # ADD: top-right controls (theme + lang toggle)
+│ ├── printers.html # CHANGE: remove form include, add "Add Printer" link
+│ ├── printers_new.html # NEW: /printers/new page
+│ ├── client_detail.html # NEW: /clients/{id} page
+│ └── partials/
+│ ├── printer_list.html # CHANGE: add Edit button + client links
+│ ├── printer_edit_modal.html # NEW: with edit form
+│ └── client_list.html # CHANGE: wrap client name in
+└── api/pages.py # ADD: GET /printers/new, GET /clients/{id}
+```
+
+### Pattern 1: HTMX PATCH Route (UIE-01)
+
+**What:** Add `PATCH /printers/{id}` to `api/printers.py`. Accepts same form fields as POST. Updates the DB record. Returns `_render_printer_list(request)` — identical to DELETE response.
+
+**When to use:** Any in-place mutation that follows the existing delete pattern.
+
+**Key implementation note:** `printer_form.html` has Alpine.js `x-data` with `ip` and `port` bound to `x-model`. Inside a ``, Alpine.js initialises normally because `` is a standard DOM element — no special wiring needed. The `portEdited` flag should be initialised to `true` in the edit modal (unlike the add form where it starts `false`) so that editing IP does not clobber a manually-set port.
+
+```python
+# In api/printers.py — add alongside existing POST and DELETE
+@router.patch("/{printer_id}", response_class=HTMLResponse)
+def update_printer(
+ request: Request,
+ printer_id: int,
+ name: str = Form(...),
+ ip_address: str = Form(...),
+ port_name: str = Form(...),
+ duplex_mode: str = Form("OneSided"),
+ color_mode: str = Form(""),
+ paper_size: str = Form("A4"),
+ collate: str = Form(""),
+ client_id: str = Form(""),
+ driver_id: str = Form(""),
+) -> HTMLResponse:
+ printer = Printer.get_or_none(Printer.id == printer_id)
+ if printer is None:
+ return _error_response(f"Printer {printer_id} not found.", status_code=404)
+ # ... validate + update fields ...
+ printer.updated_at = _utcnow() # models.py already has updated_at field
+ printer.save()
+ return _render_printer_list(request)
+```
+
+**HTMX PATCH caveat:** HTMX 2.x sends PATCH natively via `hx-patch`. FastAPI 0.115 registers `@router.patch(...)` without issue. The form inside the `` uses:
+```html
+hx-patch="/printers/{{ p.id }}"
+hx-target="#printer-list"
+hx-swap="outerHTML"
+```
+No HTMX method override (`X-HTTP-Method-Override`) is needed — HTMX sends the real HTTP method.
+
+### Pattern 2: Native `` Modal (UIE-01)
+
+**What:** Pico CSS 2.1.1 styles `` natively. Open with `dialog.showModal()`, close with `dialog.close()` or a `