From b056ff334fad3f96ca583f4d1ec70ea83d34c415 Mon Sep 17 00:00:00 2001 From: mptyl Date: Tue, 30 Jun 2026 17:53:29 +0200 Subject: [PATCH] feat(frontend): phase progress dots (B) + compact sidebar redesign (C) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B — WorkflowBar renders F1..F8 as colored ring-dots (no phase-name text): green=done, amber=running (subtle pulse), red=error, gray=pending; green connectors lead the active dot, each dot carries data-state. Lightweight error signal: sessionStore gains `phaseError`, set when an info event has level=error during the active phase, cleared on the next ui_request. C — denser single-line session rows (inline status dot + name, py-1), a 3-level type hierarchy (L1 SESSIONS / L2 section+group headers / L3 names), and the "No group" label removed (ungrouped render after the last group, guarded so the empty-state still teaches when there are zero groups). Live-verified with Playwright (all four dot states, sidebar hierarchy, and E's deferred activity-panel check). Frontend 93/93, tsc -b clean. Co-Authored-By: Claude Opus 4.8 --- PROJECT_STATE.md | 27 +++++--- .../src/shell/AppShell.session-mgmt.test.tsx | 13 ++++ frontend/src/shell/AppShell.tsx | 25 +++++--- frontend/src/shell/NavSessions.tsx | 51 ++++++++------- frontend/src/shell/WorkflowBar.test.tsx | 16 +++++ frontend/src/shell/WorkflowBar.tsx | 62 ++++++++++++------- frontend/src/store/sessionStore.test.ts | 24 +++++++ frontend/src/store/sessionStore.ts | 10 ++- 8 files changed, 160 insertions(+), 68 deletions(-) diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 942a33bb..509e049e 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -81,7 +81,8 @@ Opens frontend at http://localhost:5173 → backend :8787. ## UI/UX redesign + Resume — IN PROGRESS (2026-06-30, evening) Approved multi-workstream plan: **`~/.claude/plans/foamy-forging-dahl.md`** (read it to resume). -Memory: `thothii-ui-redesign-inprogress.md`. **D + E are merged to `main` @ `0eeb3f7` and pushed.** +Memory: `thothii-ui-redesign-inprogress.md`. **D + E merged @ `0eeb3f7` (pushed); B + C +implemented + live-verified (not yet committed as of this update).** - **D — DONE** (`c12bdcd`): session display `name` = 3-5 Italian keywords via **YAKE** (no LLM), derived in `tht session new` (CLI layer); `create_session` core unchanged (`name=None` default). @@ -89,20 +90,28 @@ Memory: `thothii-ui-redesign-inprogress.md`. **D + E are merged to `main` @ `0ee - **E — DONE** (`0eeb3f7`): rotating activity icon replaces the red dot in `CentralStatus` (inline, clickable → opens the panel); `ModelActivityPanel` is a **5-line expandable model-stream tail**; `WorkingSpinner` extracted to its own module; the separate spinner button - + orphaned `Transcript.tsx` removed. Frontend 87/87, tsc clean. **Live visual check PENDING** - (needs a *working* session — do it in the B/C pass). -- **B (pending):** `WorkflowBar` → colored **dots** F1..F8, no text (amber-translucent=running, - green=done, red=error, gray=pending); error is lightweight (store `phaseError` set on an `info` - `level=error` during the phase, cleared on the next `ui_request`). -- **C (pending):** right sidebar — tighter rows, 3-level type hierarchy via **`/impeccable`**, - remove the "No group" label (ungrouped after the last group accordion); add **Resume** to `SessionMenu`. + + orphaned `Transcript.tsx` removed. Frontend 87/87, tsc clean. **Live visual check DONE + (2026-06-30):** inline spinner opens the panel; 5-line collapsed tail; expand → full transcript. +- **B — DONE** (uncommitted): `WorkflowBar` is now colored **dots** F1..F8, no phase-name text + (amber-translucent=running, green=done, red=error, gray=pending; green connectors lead the active + dot). Each dot carries `data-state`. Error is lightweight: store `phaseError` set when an `info` + `level=error` arrives during the phase, cleared on the next `ui_request` (`sessionStore.ts`). + **All four states live-verified** via Playwright. +- **C — DONE** (uncommitted): right sidebar — single-line denser rows (inline status dot + name, + `py-1`), a 3-level type hierarchy via **`/impeccable`** (L1 `SESSIONS` red/bold/wide-tracking · + L2 section + group headers muted uppercase · L3 names normal-case), and the **"No group" label + removed** (ungrouped sessions render after the last group; guarded so the empty-state still + teaches when there are no groups). **Live-verified.** (Resume in `SessionMenu` stays with A1.) +- **Tests:** frontend **93/93** (was 87; +3 store `phaseError`, +2 `WorkflowBar` dot-state, +1 + AppShell no-"No group"), `tsc -b` clean. - **F (pending):** single-select answers **auto-confirm** — `reviewer_select` persists the decision directly on a concrete choice (no redundant `reviewer_decide` gate); contract change (update its tool desc + `SKILL.md` + the `CLAUDE.md` note). back/exit/Other stay non-persisting. - **A (pending, riskiest):** Resume command + **FIX the resume cold-start stall** (open item #1). - **G (later):** cross-model behavior matrix (Qwen3.6 / GLM 5.2 / Deepseek V4 / others). -**Next chunk:** the **B/C visual pass** (`/impeccable` + a live verification, incl. E's deferred check). +**Next chunk:** **F** (single-select auto-confirm — harness gate/SKILL contract change), then **A** +(Resume in the kebab + the resume cold-start stall fix, diagnosis-first). **G** (cross-model) later. ## Live verification + reviewer_select fix (2026-06-30, afternoon) diff --git a/frontend/src/shell/AppShell.session-mgmt.test.tsx b/frontend/src/shell/AppShell.session-mgmt.test.tsx index a8a18185..bc47645a 100644 --- a/frontend/src/shell/AppShell.session-mgmt.test.tsx +++ b/frontend/src/shell/AppShell.session-mgmt.test.tsx @@ -36,6 +36,19 @@ test("active list shows group header and hides archived sessions", async () => { expect(screen.queryByText("Archiviata due")).not.toBeInTheDocument(); // archived hidden }); +test("ungrouped sessions render after groups with no 'No group' label", async () => { + server.use( + http.get("http://localhost:8787/sessions", () => HttpResponse.json([ + { id: "g1", status: "open", question: "In gruppo", summary: null, created_at: "2026-01-02T00:00:00Z", updated_at: null, author: null, name: null, group: "Aritmologia", archived: false }, + { id: "u1", status: "open", question: "Senza gruppo", summary: null, created_at: "2026-01-03T00:00:00Z", updated_at: null, author: null, name: null, group: null, archived: false }, + ])), + ); + wrap(); + expect(await screen.findByText("In gruppo")).toBeInTheDocument(); // grouped + expect(screen.getByText("Senza gruppo")).toBeInTheDocument(); // ungrouped, still listed + expect(screen.queryByText(/no group/i)).not.toBeInTheDocument(); +}); + test("opening the panel shows the session documents", async () => { wrap(); await userEvent.click(await screen.findByText("Attiva uno")); diff --git a/frontend/src/shell/AppShell.tsx b/frontend/src/shell/AppShell.tsx index cb925c48..29885232 100644 --- a/frontend/src/shell/AppShell.tsx +++ b/frontend/src/shell/AppShell.tsx @@ -50,6 +50,7 @@ export function AppShell() { [sessions], ); const activeList = sessions.filter((s) => !s.archived); + const ungroupedActive = activeList.filter((s) => !s.group); const archivedList = sessions.filter((s) => s.archived); const refresh = () => queryClient.invalidateQueries({ queryKey: ["sessions"] }); @@ -227,17 +228,19 @@ export function AppShell() { + {/* L1 — rail title */}
- + Sessions
+ {/* L2 — section toggle */}
))} -
- {groups.length > 0 && ( -

No group

- )} + {/* Ungrouped sessions list directly after the groups — no "No group" label. + When there are no groups at all, still render it so its empty state can + teach first-time users. */} + {(ungroupedActive.length > 0 || groups.length === 0) && ( !s.group)} + sessions={ungroupedActive} activeSessionId={activeSessionId} onOpenPanel={openPanel} menuFor={menuFor} /> -
+ )} )} + {/* L2 — section toggle */}