# Workflow UI Regressions Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Restore Phase 1 model activity, make join review read-only, improve CTE presentation, and prevent malformed phase summaries from crashing the UI. **Architecture:** Preserve the existing Pi→backend→SSE→React pipeline while introducing one explicit `activity_delta` event and one explicit `join-review` widget contract. Enforce model-authored artifact shapes at the gate and retain defensive rendering for legacy payloads. **Tech Stack:** TypeScript, Fastify, React 18, Zustand, Vitest/Testing Library, Node test runner, Pi gate extension, Docker Compose. ## Global Constraints - UI strings remain English; persisted workspace content remains in the workspace language. - The harness remains the owner of workflow decisions and persistence. - Joins are informational and are persisted as a complete set only after `Continue`. - `Other — specify` is the only join-editing path and must not persist the rejected proposal. - F3 rewrite approval remains automatic with no reviewer widget. - No verbatim reasoning is persisted to session artifacts. --- ### Task 1: Restore the Model Activity stream **Files:** - Modify: `backend/test/routes-sessions.test.ts` - Modify: `backend/test/session-bridge.test.ts` - Modify: `backend/src/routes/sessions.ts` - Modify: `backend/src/bridge/session-bridge.ts` - Modify: `frontend/src/api/types.ts` - Modify: `frontend/src/store/sessionStore.ts` - Modify: `frontend/src/store/sessionStore.test.ts` - Modify: `frontend/src/shell/ModelActivityPanel.tsx` - Modify: `frontend/src/shell/ModelActivityPanel.test.tsx` **Interfaces:** - Produces: `ClientEvent`/`StreamEvent` variant `{ type: "activity_delta"; text: string }`. - Produces: Zustand `activity: Entry[]`, consumed by `ModelActivityPanel`. - [x] **Step 1: Write backend failing tests** Add route assertions showing create passes the configured `thinking` level and resume passes the manifest level. Add a bridge test that emits: ```ts rpc.emit("event", { type: "message_update", assistantMessageEvent: { type: "thinking_delta", delta: "reasoning" }, }); expect(events).toContainEqual({ type: "activity_delta", text: "reasoning" }); ``` - [x] **Step 2: Run backend tests and verify RED** Run: `cd backend && npx vitest run test/routes-sessions.test.ts test/session-bridge.test.ts` Expected: failures show `thinking` is still `off` and no `activity_delta` is emitted. - [x] **Step 3: Implement backend event/config changes** Use `thinking: s.thinking` for new sessions and `thinking: saved?.thinking ?? settings.thinking` on resume. Extend the bridge event union and map nested `thinking_delta` to `activity_delta` without changing final `text_delta` behavior. - [x] **Step 4: Write frontend failing tests** Assert `applyEvent({ type: "activity_delta", text: "reasoning" })` appends to `activity` and not `transcript`; render the activity panel after applying only `activity_delta` and expect the text. - [x] **Step 5: Run frontend tests and verify RED** Run: `cd frontend && npx vitest run src/store/sessionStore.test.ts src/shell/ModelActivityPanel.test.tsx` Expected: TypeScript/test failures because the event variant and activity state do not exist. - [x] **Step 6: Implement frontend activity state** Add the event type, `activity` state, append logic mirroring streamed entry accumulation, reset it with the session, and have the panel select `state.activity`. - [x] **Step 7: Run targeted tests and typechecks** Run: `cd backend && npx vitest run test/routes-sessions.test.ts test/session-bridge.test.ts && npx tsc --noEmit -p .` Run: `cd frontend && npx vitest run src/store/sessionStore.test.ts src/shell/ModelActivityPanel.test.tsx && npx tsc -b` Expected: PASS. ### Task 2: Replace editable join selection with read-only review **Files:** - Modify: `harness/.pi/extensions/gate/builders.js` - Modify: `harness/.pi/extensions/gate/__tests__/builders.test.js` - Modify: `harness/.pi/extensions/tht-gate.js` - Create: `harness/.pi/extensions/gate/__tests__/gate_join_review.test.js` - Modify: `harness/.pi/skills/tht-sessione/SKILL.md` - Modify: `harness/tht/decisions.py` - Modify: `harness/tht/cli/decision_cmd.py` - Create: `harness/tests/test_decision_join_set_cli.py` - Create: `frontend/src/widgets/JoinReviewWidget.tsx` - Create: `frontend/src/widgets/JoinReviewWidget.test.tsx` - Modify: `frontend/src/widgets/index.ts` - Modify: `frontend/src/api/types.ts` - Modify: `frontend/src/widgets/registry.test.tsx` **Interfaces:** - Produces: `buildJoinReviewRequest({ id, phase, title, options })` with widget `join-review`. - Consumes: options `{ id, label, detail?, rationale? }`. - Produces: response `{ id, kind: "join-review", choices: allOptionIds }` on `Continue`. - [x] **Step 1: Write failing builder and gate tests** Assert the builder emits read-only option details, `confirm_label: "Continue"`, and reserved controls. Exercise `reviewer_decide` with only `join_modified` decisions; queue a Continue response containing all ids and assert one atomic `tht decision add-join-set` call occurs. Queue malformed and partial responses and assert the widget is presented again. Queue `control:"freetext"` and assert no decision is written. - [x] **Step 2: Run harness tests and verify RED** Run: `cd harness && node --test .pi/extensions/gate/__tests__/builders.test.js .pi/extensions/gate/__tests__/gate_join_review.test.js` Expected: builder/export/widget contract is missing and join calls still emit `multiselect`. - [x] **Step 3: Implement builder and gate routing** Add `buildJoinReviewRequest`. In `reviewer_decide`, detect a non-empty, join-only merit list: ```js const joinOnly = opts.length > 0 && opts.every((o) => o.decision.type === "join_modified"); ``` Emit `join-review` with `detail` and `rationale`; accept only a response carrying the exact complete id set. After Continue persist all original decisions through atomic `decision add-join-set`, with a per-session cross-process lock covering ledger read, sequence assignment, and replacement. On free text, return feedback without persistence. Keep all other decisions on `multiselect`. - [x] **Step 4: Write failing frontend widget tests** Render two join cards and assert there are no checkboxes. Click `Continue` and expect all ids in the response. Open `Other — specify`, submit correction text, and expect a freetext control response. - [x] **Step 5: Run frontend widget tests and verify RED** Run: `cd frontend && npx vitest run src/widgets/JoinReviewWidget.test.tsx src/widgets/registry.test.tsx` Expected: widget and registry entry are missing. - [x] **Step 6: Implement and register JoinReviewWidget** Render semantic cards with label, detail, and rationale, one `Continue` primary button, and `ReservedControls`. Register `join-review` and extend `WidgetOption` with optional `detail` and `rationale` strings. - [x] **Step 7: Update model instructions and verify targeted tests** Document that joins must be a separate join-only `reviewer_decide` call; the reviewer cannot remove individual joins and textual corrections require a complete revised proposal. Run: `cd harness && npm test` Run: `cd frontend && npx vitest run src/widgets/JoinReviewWidget.test.tsx src/widgets/registry.test.tsx && npx tsc -b` Expected: PASS. ### Task 3: Improve CTE plan layout and hide inert SQL controls **Files:** - Modify: `frontend/src/viewers/CtePlanViewer.tsx` - Modify: `frontend/src/viewers/CtePlanViewer.test.tsx` - Modify: `frontend/src/viewers/SqlViewer.tsx` - Modify: `frontend/src/viewers/SqlViewer.test.tsx` - Modify: `frontend/src/viewers/CteResultViewer.test.tsx` **Interfaces:** - `SqlViewer({ blocks })` shows the layout toggle only when `blocks.length > 1`. - CTE plan data shape remains unchanged. - [x] **Step 1: Write failing semantic/layout tests** Assert long filter data is rendered in distinct elements labeled Column, Operator, and Value; assert card sections expose stable headings. Assert a one-block SQL viewer has no Horizontal or Vertical controls while a two-block viewer retains both. - [x] **Step 2: Run tests and verify RED** Run: `cd frontend && npx vitest run src/viewers/CtePlanViewer.test.tsx src/viewers/SqlViewer.test.tsx src/viewers/CteResultViewer.test.tsx` Expected: structured filter labels are absent and the one-block toggle is present. - [x] **Step 3: Implement responsive CTE cards** Use a bordered header grid, prose blocks with `leading-relaxed`, metadata rows with fixed labels, `break-words`/`font-mono` for physical identifiers, and a responsive filter grid such as `grid-cols-1 sm:grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)]`. Keep output columns wrapping. - [x] **Step 4: Hide the single-block toggle** Wrap the layout control in `{blocks.length > 1 && (...)}` without changing multi-block state or rendering. - [x] **Step 5: Run targeted tests and typecheck** Run: `cd frontend && npx vitest run src/viewers/CtePlanViewer.test.tsx src/viewers/SqlViewer.test.tsx src/viewers/CteResultViewer.test.tsx && npx tsc -b` Expected: PASS. ### Task 4: Harden phase summaries against malformed open questions **Files:** - Modify: `harness/.pi/extensions/gate/artifact-contracts.js` - Modify: `harness/.pi/extensions/gate/__tests__/artifact_contracts.test.js` - Modify: `harness/.pi/skills/tht-sessione/SKILL.md` - Modify: `frontend/src/viewers/artifactV2.ts` - Modify: `frontend/src/viewers/PhaseSummaryViewer.tsx` - Modify: `frontend/src/viewers/PhaseSummaryViewer.test.tsx` - Modify: `frontend/src/viewers/ArtifactView.test.tsx` **Interfaces:** - Gate contract: `open_questions?: string[]`. - Frontend legacy normalization: string entries pass through; objects prefer `question`, then `label`; all other values become safe text or are omitted. - [x] **Step 1: Write failing gate validation test** Pass the exact observed payload shape: ```js open_questions: [{ label: "pazienti_finale restituisce 0 righe", question: "Verificare i filtri" }] ``` Expect `ok:false` and an error naming `open_questions[0]`. - [x] **Step 2: Run gate test and verify RED** Run: `cd harness && node --test .pi/extensions/gate/__tests__/artifact_contracts.test.js` Expected: payload is currently accepted. - [x] **Step 3: Implement strict gate validation** Reject a non-array `open_questions` value and every non-string entry with an indexed error. Document the exact array-of-strings shape in the session skill. - [x] **Step 4: Write failing frontend resilience test** Render a phase-summary artifact containing the observed object and assert the screen displays `Verificare i filtri` and does not show the ErrorBoundary fallback. - [x] **Step 5: Run frontend test and verify RED** Run: `cd frontend && npx vitest run src/viewers/PhaseSummaryViewer.test.tsx src/viewers/ArtifactView.test.tsx` Expected: React reports an object child/rendering failure. - [x] **Step 6: Implement safe legacy normalization** Add a small `phaseOpenQuestionText(value: unknown): string | null` helper and map/filter entries before rendering. Preserve the strict public TypeScript contract for new v2 payloads. - [x] **Step 7: Run targeted tests and typechecks** Run: `cd harness && npm test` Run: `cd frontend && npx vitest run src/viewers/PhaseSummaryViewer.test.tsx src/viewers/ArtifactView.test.tsx && npx tsc -b` Expected: PASS. ### Task 5: Full verification, durable notes, and Docker deployment **Files:** - Modify: `PROJECT_STATE.md` - Modify or create under `brain/codebase/` and update `brain/index.md` if the vault is present. **Interfaces:** - Running Compose services must use the newly built `thothii-core:local` and `thothii-frontend:local` image ids. - [x] **Step 1: Run all regression suites** Run: `cd backend && npx vitest run && npx tsc --noEmit -p . && npm run build` Run: `cd frontend && npx vitest run && npx tsc -b && npm run build` Run: `cd harness && npm test` Expected: all pass. - [x] **Step 2: Update project state and durable architectural note** Record the implemented contracts, verification counts, deployment state, and the distinction between building an image and recreating a service. - [x] **Step 3: Build and recreate Compose services** Run: `docker compose build` Run: `docker compose up -d --force-recreate` Expected: both services are recreated from the new images. - [x] **Step 4: Verify deployed containers** Run: `docker compose ps` Run: `docker inspect thothii-core-1 thothii-frontend-1 --format '{{.Name}} {{.Image}} {{.State.Status}}'` Expected: both report the new image ids and `running`; the core healthcheck reports `healthy` (the frontend image has no healthcheck). - [x] **Step 5: Review diff and commit intentionally** Run: `git diff --check && git status --short && git diff --stat` Commit only the files in this plan with a scoped message after review.