Files
ThothII/docs/superpowers/specs/2026-07-03-workflow-ui-fixes-design.md
T

189 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Workflow UI fixes — design
Date: 2026-07-03
Status: approved (brainstorming), pending plan
Five defects surfaced while testing the app. All are UI/contract issues in the
frontend, plus one root-cause fix in the harness gate. No changes to the Pi RPC
wire protocol.
## Scope
| # | Problem | Fix location |
|---|---------|--------------|
| 1 | Phase circles don't reflect lifecycle (F1 never turns yellow at start; no explicit green/red on end) | frontend |
| 2 | No total elapsed-time indicator after the 8 circles | frontend |
| 3 | Reviewer artifact gate shows nothing (revised question, schema link, CTEs, final SQL) | frontend (contract fix) |
| 4 | Complex artifacts need a 90% modal, fully formatted, with a Mermaid diagram for schema linking | frontend |
| 5 | Both "Altro - specificare" and "other" appear in dialogs | harness |
---
## Problem 5 — Duplicate "Altro"/"other" (root cause)
**Cause.** `builders.js` always sets `reserved: ["back","exit","other"]`, which the
frontend renders as the `Other — specify` control. Separately, `reviewer_select`
and `reviewer_decide` strip model-supplied options via `isReserved(o.label)`
(`reserved-labels.mjs`), but `isReserved` does an **exact-string** match against
`ALTRO = "Altro — specifica…"`. The model emits variants (`"Altro - specificare"`,
plain hyphen; `"altro"`; English `"Other — specify"`) that slip through the exact
match, so both the model's free-text option **and** the reserved control appear.
**Fix.** Make `isReserved(label)` robust in `reserved-labels.mjs`:
- Normalize: lowercase, strip diacritics (NFD + remove combining marks), collapse
punctuation/whitespace, trim.
- Treat as reserved if the normalized label **starts with** `altro` or `other`, or
equals the normalized canonical back/exit labels (`QUIT_LABEL`, `BACK_LABEL`).
`stripReserved` and both reviewer tools inherit the fix (they call `isReserved`).
Result: only the reserved `Other — specify` control remains (English, per the UI
convention that chrome/labels stay English).
**Tests.** Unit test in the harness reserved-labels test: variants
`"Altro - specificare"`, `"altro"`, `"ALTRO — specifica…"`, `"Other — specify"` are
all reserved; a normal merit option (`"procedura"`) is not.
---
## Problem 3 — Artifact gate renders nothing (contract mismatch)
**Cause.** The harness sends `artifact: { kind, data, version }` (see
`tht-gate.js` `reviewer_confirm` → `buildArtifactGate`, golden
`artifact_gate_F5.json`). The frontend `ArtifactGateWidget` reads
`descriptor.artifact?.content`, which never exists → nothing renders. It is a
`data` vs `content` mismatch, not missing data.
**Fix.** Rendering reads `artifact.data` and switches on `artifact.kind`. This is
subsumed by the Problem 4 modal: the old `<pre>{artifact.content}</pre>` is
replaced by `ArtifactView` (below).
---
## Problem 4 — 90% modal + ArtifactView + erDiagram
### ArtifactGateModal
New `frontend/src/widgets/ArtifactGateModal.tsx`, built on the existing Radix
dialog (`components/ui/dialog.tsx`).
- Size: `90vw × 90vh`.
- Layout: **top** = scrollable artifact area (`flex-1`, `overflow-auto`) rendering
`ArtifactView`; **bottom** = action bar: the gate `options` buttons
(`Conferma e prosegui` / `Rifiuta`) plus `ReservedControls` (Other/Back/Exit).
- The `option.opens → freetext` linkage keeps working via the existing
`LinkageHost`.
- Auto-opens when `pendingWidget.widget === "artifact-gate"`. The `select` /
`multiselect` / `freetext` gates stay inline as today.
- Routing: `WidgetHost` (or the widget registry) renders artifact-gate through the
modal instead of the inline `ArtifactGateWidget` card. The inline
`ArtifactGateWidget` body is replaced by the modal; its option/linkage logic is
reused.
### ArtifactView
New `frontend/src/viewers/ArtifactView.tsx`. Switches on `artifact.kind` and is
**defensive** about `data` (accepts a string or an object, picks common fields,
falls back to formatted JSON):
| `artifact.kind` | Renderer |
|-----------------|----------|
| `schema_linking` | `SchemaLinkingViewer` (data = linking object) |
| `sql`, `cte_result` | `SqlViewer` (string or `{sql}` / `{ctes[], final}`) |
| `cte_plan` | ordered list of CTE names (`data` = array or `{names[]}`) |
| `question`, `phase` | `MarkdownView` (string or `{markdown}`/`{question}`) |
| default / unknown | pretty-printed JSON in a formatted `<pre>` |
### Schema-linking diagram → Mermaid erDiagram
In `SchemaLinkingViewer.tsx`, replace `buildFlowchart` with `buildErDiagram`
producing a Mermaid `erDiagram`:
- Entities = promoted **tables**; each table's promoted **columns** (name format
`table.column`) become entity attributes.
- Relationships = `joins` between promoted tables.
- **Relations are drawn only if `artifact.data.joins` is present.** If the model
supplies no joins, tables render without edges. This intervention does **not**
change the harness/model to force joins into `schema_linking`.
- Keep the existing table-view fallback and the oversized-graph cap.
This viewer is shared with `SessionDocumentsPanel`, which improves consistently.
---
## Problem 1 — Phase-circle lifecycle (frontend-optimistic)
`currentPhase` is set only on `ui_request.phase` (`sessionStore.ts`), so before
F1's first gate (the 3–4 min cold start) no circle is yellow; and "done/green" is
positional (`i < activeIdx`), so the last phase never greens and there is no
explicit end signal.
**Fix, in `WorkflowBar.tsx` + the new-question submit path:**
- **Yellow at start.** On **new-question** creation, set `currentPhase = "F1"`
immediately (in the submit path — `SteerInput` / store action), so F1 is yellow
during cold start. On **resume**, leave `currentPhase` null until the first gate
(the phase is unknown until then).
- **Green on end.** Keep positional green while advancing. Additionally, when the
active session is `finalized`, mark **all 8** circles `done` (green).
- **Red on error.** Unchanged: `phaseError` (set on `info` level `error`) drives
the current phase's red state until the next gate.
- `WorkflowBar` receives `finalized` from `AppShell` (derived from the active
session's status).
Positional inference already handles multi-gate phases, auto-advanced phases
(empty memory F2), and the reviewer `back` control (recomputed each render).
---
## Problem 2 — Total elapsed timer
New `frontend/src/shell/ElapsedTimer.tsx`, rendered after the 8 circles inside the
workflow strip.
- Anchor = active session's `created_at` (robust to reload/resume).
- Ticks every second while not `finalized`; freezes at `updated_at − created_at`
once `finalized`.
- Fallback: if `created_at` is not yet available (brand-new session before the
sessions-list poll returns it), anchor on a local timestamp captured when the
session became active.
- Format: `Xm Ys`.
- `AppShell` passes `createdAt` / `finalized` to `WorkflowBar`, which lays out the
circles (centered) and the timer (trailing).
---
## Files touched
**Frontend**
- `shell/WorkflowBar.tsx` — accept `finalized`/`createdAt`; optimistic F1; all-green
on finalized; render `ElapsedTimer`.
- `shell/AppShell.tsx` — pass active session `created_at`/`finalized` to `WorkflowBar`.
- new-question submit path (`shell/SteerInput.tsx` and/or `store/sessionStore.ts`) —
set `currentPhase = "F1"` on new question.
- new `shell/ElapsedTimer.tsx`.
- new `widgets/ArtifactGateModal.tsx`; `widgets/index.ts` / `shell/WidgetHost.tsx`
route artifact-gate to the modal.
- new `viewers/ArtifactView.tsx`.
- `viewers/SchemaLinkingViewer.tsx` (+ `viewers/mermaid.ts` if needed) — erDiagram.
**Harness**
- `.pi/extensions/reserved-labels.mjs` — normalized `isReserved`.
- reserved-labels test — variant coverage.
## Testing
- Harness: `node --test` on the reserved-labels test (variant coverage);
existing `builders`/gate tests stay green.
- Frontend: `npx vitest run` — update/keep `ArtifactGateWidget` / f1-loop /
sessionStore tests; add tests for `ArtifactView` kind routing, `ElapsedTimer`
formatting/freeze, `WorkflowBar` optimistic-F1 + finalized-all-green, and the
erDiagram builder. `npx tsc -b` clean.
## Out of scope
- No Pi RPC wire-protocol change; no authoritative phase-lifecycle events from the
harness (chosen: frontend-optimistic).
- No harness/model change to force `joins` into `schema_linking`.
- Reserved control label stays English (`Other — specify`) per UI convention.