Files
ThothII/docs/superpowers/plans/2026-07-14-workflow-ui-regressions.md
T

315 lines
13 KiB
Markdown

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