docs(P08): complete 2nd final review + audit + approval-gate phase (#8)
Squash-merge of phase/08-final-review-approval-ship into milestone/v0.1-prototype. Review PASS-WITH-FIXES (double-compose + drawer-keyboard P1s, 90/90). Audit PASS-WITH-NOTES (all applied). Tag v0.1.8 = extended-prototype release candidate. Milestone merge to main HALTS at the REQ-62 owner approval gate.
This commit was merged in pull request #8.
This commit is contained in:
@@ -47,7 +47,7 @@ FastAPI routes: POST /api/runs, GET /api/methodologies, GET /api/runs/{id}, appr
|
||||
- **DependsOn:** engine, methodologies, config
|
||||
|
||||
### 7. Frontend (Next.js App Router, TypeScript strict)
|
||||
App shell (root layout, P5): persistent sidebar (primary nav — New research / Library / Settings; adapter connection-status dots as a v2 preview) + top bar (⌘K navigation palette — screens + stored reports; avatar button → user menu with the local single-user profile). Screens: Ask (question + optional context), Choose (methodology cards with ETA/cost/why-recommended; dials behind "advanced"), Plan & Run (approve/edit plan, live SSE trace, pause/cancel), Reading Room v0 (rendered report, hover-a-citation → exact quoted passage, ledger panel, Markdown/JSON export), Library (P5: folder-structure report storage — auto-categorized folders, recency/title sorting, report cards into the Reading Room; cross-report Connections panel — shared-sources ⇄ / related-topic → edges; project graph + task inbox as v2-preview panels), Settings (P5: masked API-key status, storage directory, default methodology + resolved 12-dial display, appearance preview, about/version).
|
||||
App shell (root layout, P5): persistent sidebar (primary nav — New research / Library / Settings; adapter connection-status dots as a v2 preview) + top bar (⌘K navigation palette — screens + stored reports; avatar button → user menu with the local single-user profile). Screens: Ask (question + optional context; open-question suggestions below the form — follow-ups from unanswered questions of past runs, click-fills-the-box, REQ-67; opt-in Refine dialogue — inline drawer, scripted clarifying exchange, composed refined question + suggested context, REQ-68), Choose (methodology cards with ETA/cost/why-recommended; dials behind "advanced"), Plan & Run (approve/edit plan, live SSE trace, pause/cancel), Reading Room v0 (rendered report, hover-a-citation → exact quoted passage, ledger panel, Markdown/JSON export), Library (P5: folder-structure report storage — auto-categorized folders, recency/title sorting, report cards into the Reading Room; cross-report Connections panel — shared-sources ⇄ / related-topic → edges; project graph + task inbox as v2-preview panels), Settings (P5: masked API-key status, storage directory, default methodology + resolved 12-dial display, appearance preview, about/version).
|
||||
|
||||
- **Boundaries:** consumes the typed `ApiClient` interface only — components never import the mock client directly. In v0.1 the interface is bound to `MockApiClient` (fixture data + scripted event replay); in v0.2 phase 3 the binding swaps to the OpenAPI-generated client with no component changes.
|
||||
- **DependsOn:** ApiClient contract (v0.1: mock; v0.2: API layer)
|
||||
@@ -58,7 +58,7 @@ v0.1 (prototype): Ask (question + context) → Choose (methodology slug + dial o
|
||||
v0.2 (MVP): Ask (question + context) → Choose (methodology slug + dial overrides) → POST /api/runs → engine PLANNING (planner model via model adapter, dial bundle resolved) → AWAITING_APPROVAL (plan persisted; SSE events; user approves/edits) → SEARCHING (search adapter, routing per dials) → EXTRACTING (fetch pages → evidence store, stable passage IDs) → REFLECTING (planner model; depth loop per dials) → SYNTHESIZING (writer model; context = evidence passages only, per-claim citation markers) → VERIFYING (liveness pass, quote anchoring, confidence labels) → DONE (report + ledger + checklist persisted to storage; Reading Room renders; export).
|
||||
|
||||
## Build Order
|
||||
Milestone v0.1 (prototype): 1. Frontend scaffold + ApiClient/MockApiClient boundary → 2. Ask + Choose → 3. Plan & Run + Reading Room v0 → 4. v2 mockups + polish + 3-click validation → 5. review + owner approval gate (owner returned gate feedback → extension cycle: 5. app shell + research library + settings + avatar → 6. final review + approval re-presentation + ship).
|
||||
Milestone v0.1 (prototype): 1. Frontend scaffold + ApiClient/MockApiClient boundary → 2. Ask + Choose → 3. Plan & Run + Reading Room v0 → 4. v2 mockups + polish + 3-click validation → 5. review + owner approval gate (owner returned gate feedback → extension cycle: 5. app shell + research library + settings + avatar → 6. final review + approval re-presentation (2nd owner gate feedback: 7. ask suggestions + refine dialogue → 8. final re-review + ship)).
|
||||
|
||||
Milestone v0.2 (MVP, gated on v0.1 approval):
|
||||
1. Methodology framework (dial schema + 2 seed methodologies + registry)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"phase": 8,
|
||||
"stage": "execute",
|
||||
"stage": "approval_pending",
|
||||
"milestone": "v0.1",
|
||||
"phase_role": "final",
|
||||
"attempts": 0,
|
||||
"updated_at": "2026-09-27T05:15:00Z"
|
||||
}
|
||||
"updated_at": "2026-09-27T05:35:00Z"
|
||||
}
|
||||
+34
-28
@@ -1,48 +1,54 @@
|
||||
# PLAN — Milestone v0.1 Phase 7: Ask Screen Intelligence — Suggestions + Refine
|
||||
# PLAN — Milestone v0.1 Phase 8 (FINAL): Review + Approval Re-presentation + Ship
|
||||
|
||||
---
|
||||
phase: 7
|
||||
phase: 8
|
||||
milestone: v0.1
|
||||
status: planned
|
||||
status: executed
|
||||
must_haves:
|
||||
- "Follow-up query suggestions on the New Research view, derived from unanswered questions (open task-inbox items joined with source reports); click fills the question box — editable, never auto-starts (REQ-67)"
|
||||
- "Refine option: opt-in back-and-forth dialogue in an inline drawer (scripted clarifying questions → answers → composed refined question + suggested context); accept fills the form, or keep original (REQ-68)"
|
||||
- "Both features opt-in — zero required clicks added to the ≤3-click happy path (pinned by test)"
|
||||
- "All new data access extends the ApiClient boundary + MockApiClient fixtures (REQ-53/54 discipline)"
|
||||
- "Full-milestone 2nd final review: PASS-WITH-FIXES — double-compose race + dead drawer-keyboard P1s fixed with regression tests (90/90); P6/P7 fixes verified holding"
|
||||
- "Audit: PASS-WITH-NOTES — reconstruction, tags, file/branch/commit discipline, secrets sweep green; notes applied (ARCHITECTURE §7 + Build Order, root README /ask row, VERSIONING table, About string, package.json version)"
|
||||
- "Final phase patch v0.1.8 shipped — milestone release candidate, presented at the REQ-62 owner approval gate with UAT staged per owner cadence"
|
||||
personas:
|
||||
- frontend-engineer
|
||||
- ci-lead-developer
|
||||
requirements: [REQ-67, REQ-68]
|
||||
requirements: [REQ-62]
|
||||
---
|
||||
|
||||
## User-Facing Surface
|
||||
|
||||
1. **Ask suggestions** (`/ask`, below the primary form): "Pick up an open question" — cards for each open task-inbox item (question text + source report title + "from your research" attribution). Click fills the question textarea (editable, no auto-submit, focus moves to the box). Honest empty state: "No open questions yet — they collect here as you run research."
|
||||
2. **Refine drawer** (`/ask`, inline): "Refine question" button (disabled until a draft exists) opens the established dials-drawer pattern — one clarifying question at a time with answer input, progress indicator (1/2/3), then the composed refined question + suggested context preview. **Use this** fills question + context and closes; **Keep original** dismisses; **Restart** re-runs the exchange. Escape closes. Honest caption: "Scripted refinement in the prototype — model-driven refine lands in v0.2."
|
||||
The extended prototype presented for owner approval: everything from v0.1.6 (shell, library, settings, avatar) plus the v0.1.7 ask-intelligence layer — open-question suggestions and the refine dialogue on the New Research view. Releases v0.1.0–v0.1.8; v0.1.8 is the extended-prototype release candidate.
|
||||
|
||||
## Happy Path
|
||||
## Happy Path (owner review script)
|
||||
|
||||
1. `cd frontend && pnpm install && pnpm dev` → http://localhost:3000/ask (shell present)
|
||||
2. Default path unchanged: question → Research this (click 1) → Choose → Start research (click 2) → Approve plan (click 3)
|
||||
3. Suggestions: scroll to "Pick up an open question" → click a card → question box fills with the open question → edit if desired → Research this as usual
|
||||
4. Refine: type a draft → "Refine question" → answer the 3 clarifying questions → review the composed question + suggested context → "Use this" → both boxes filled → Research this
|
||||
5. Keep-original: open the drawer, answer partway, Escape → the original draft is untouched
|
||||
1. `cd frontend && pnpm install && pnpm dev` → http://localhost:3000 → shell renders on /ask
|
||||
2. Ask → question → Research this (click 1)
|
||||
3. Choose → Deep Report card → Start research (click 2)
|
||||
4. Plan & Run → Approve plan (click 3) → live trace → pause/resume/cancel
|
||||
5. Read the report → hover [n] → exact quote; ledger; export Markdown/JSON
|
||||
6. New research → click an open-question suggestion → box fills (no auto-start) → edit → Research this
|
||||
7. New research → type a draft → Refine question → answer the 3 clarifying questions (skip the optional two) → Use this → question + context filled → Research this
|
||||
8. Library → folder tree → stored report opens its own report; Connections panel; ⌘K palette
|
||||
9. Avatar → user menu → Settings → API keys masked, methodology defaults live preview
|
||||
10. Owner decision: approve → v0.1.8 published + milestone merge to main; or request changes
|
||||
|
||||
## What this phase did
|
||||
|
||||
1. **Review** (multi-persona, P7-weighted, probe-verified): verdict PASS-WITH-FIXES. P1s: Enter at the last exchange question composed twice (stale section-level keydown guard re-ran submitAnswer after the input guard advanced — invisible with the deterministic mock, a doubled paid POST the day v0.2 wires the endpoint) and Enter on focused drawer buttons was dead (section preventDefault swallowed the click). Fixed: single Enter path (input guard owns Enter; buttons self-activate); error state gained a Keep original button (mouse-user trap). 2 regression tests (single-compose spy, Enter-on-button); suite 88 → 90. Adversarial probe: no drawer control can submit the outer form. P6/P7 fixes verified holding.
|
||||
2. **Audit**: PASS-WITH-NOTES — tags v0.1.0–v0.1.7 verified (annotated, on-branch, mapped to ship records); DEPLOY-RECORD.md tracked + accurate; branch hygiene exact; commit discipline clean (---/ci--- closer holding since the P6 regression); secrets clean across history; REQ-67/68 spot-checked as genuinely delivered; gates re-run live (90/90). Notes applied: ARCHITECTURE.md §7 + Build Order extended with the ask-intelligence layer; root README /ask row updated; VERSIONING table → v0.1.8; settings About + package.json → v0.1.8.
|
||||
3. **Ship (this commit sequence)**: PR #8 (phase/08 → milestone), squash-merge, tag v0.1.8, release with full milestone summary, v0.1.8-uat staged on the LXC host per owner cadence. Per REQ-62 and the prototype_approval escalation hook, the milestone merge to main does NOT proceed autonomously — the pipeline halts at the gate for the recorded owner decision.
|
||||
|
||||
## UX Acceptance Criteria
|
||||
|
||||
- ≤3-click default happy path still green (both features opt-in; zero required interactions)
|
||||
- Suggestions never compete with the primary action (below the form, not modal, no auto-start)
|
||||
- Refine drawer: draft visible behind, Escape-safe, honest scripted-prototype caption
|
||||
- Keyboard accessible; focus ring in accent (global standard)
|
||||
- All data via `api.*` — components never import MockApiClient; v0.2 OpenAPI swap unaffected
|
||||
- Owner demo script runs clean on the shipped code (probe- and test-verified)
|
||||
- Suggestions + refine never auto-submit; drafts stay untouched on dismiss
|
||||
- The 3-click criterion still pinned green on AskView (affordances opt-in)
|
||||
- Honest captions everywhere; keyboard accessible throughout
|
||||
|
||||
## Waves
|
||||
## Verification
|
||||
|
||||
1. **T-7.1 Boundary**: types (`SuggestedQuery`, `RefinementQuestion`, `RefinedQuestion`) → ApiClient gains `getSuggestedQueries` (mock: open `taskInboxFixture` items joined with `reportLibraryFixture`; v0.2: GET /api/suggestions), `getRefinementQuestions(draft)` (mock: scripted 3-question set; v0.2: POST /api/refine step 1), `composeRefinedQuestion(draft, answers)` (mock: deterministic composer; v0.2: model-driven) → co-located boundary tests
|
||||
2. **T-7.2 Suggestions UI**: `AskSuggestions` component on /ask; click-fills-box contract; empty state; tests
|
||||
3. **T-7.3 Refine drawer**: `RefineQuestionButton` + drawer component; exchange state machine; use/keep/restart; tests
|
||||
4. **T-7.4 Regression + polish**: 3-click re-pin, README, typography pass, gates green
|
||||
- Gates: typecheck ✓ lint ✓ tests 90/90 ✓ build ✓ (re-run after review fixes, plus by audit)
|
||||
- Regression tests pin: single-compose, Enter-on-button, focus return, Enter-guard, no-auto-submit
|
||||
- Post-hoc v0.2 log: compose-endpoint idempotency key; freeze draft at drawer-open; confirm-overwrite when context non-empty; Try-again from error state; textarea answers; aria-live progress
|
||||
|
||||
## Scope Guard
|
||||
|
||||
P8 (final review + approval re-presentation) follows P7 ship + UAT deploy. Milestone merge to main remains gated on the owner's recorded approval (REQ-62, prototype_approval escalation hook). No backend work, no v0.2 scope, no model calls (scripted refine only — the model-driven version is v0.2).
|
||||
Milestone merge to main, v0.1.8 publication, v0.2 unblocking, and all v0.2 P1 work wait on the owner approval gate (REQ-62, prototype_approval escalation hook). The pipeline does not proceed autonomously past this commit.
|
||||
@@ -8,7 +8,7 @@ as established by the CIAgent branch strategy and `config.json`.
|
||||
|
||||
| Milestone | Tags | Deployed by |
|
||||
|---|---|---|
|
||||
| v0.1 prototype | v0.1.0 … v0.1.5 | `bash .ciagent/release/deploy.sh v0.1.5` |
|
||||
| v0.1 prototype | v0.1.0 … v0.1.8 | `bash .ciagent/release/deploy.sh v0.1.8` |
|
||||
| v0.2 MVP | v0.2.0 … | `bash .ciagent/release/deploy.sh v0.2.x` |
|
||||
|
||||
## Rules
|
||||
@@ -33,7 +33,7 @@ as established by the CIAgent branch strategy and `config.json`.
|
||||
6. **Milestone gating applies to deployment too.** The v0.1 prototype
|
||||
deploys for owner review. v0.2 releases deploy only after the
|
||||
REQ-62 owner-approval gate is cleared (recorded on the extended
|
||||
prototype, v0.1.6).
|
||||
prototype, v0.1.7 → released as v0.1.8).
|
||||
|
||||
## Container conventions
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ Three clicks from cold start to initiated research.
|
||||
|
||||
| Screen | What it does |
|
||||
|---|---|
|
||||
| `/ask` | One question box + one optional context box. Nothing else competing for attention. |
|
||||
| `/ask` | One question box + one optional context box — plus two opt-in helpers below and beside them: open-question suggestions (follow-ups from your past runs, one click fills the box) and the Refine question dialogue (a few clarifying answers → a composed, sharper question). Neither ever auto-starts. |
|
||||
| `/choose` | All 8 locked methodology cards — mock ETA, cost estimate, why-recommended, and the 12 methodology dials behind each card's Advanced drawer. |
|
||||
| `/run/[id]` | Plan & Run: approve the research plan (sub-questions, source universe, outline), watch the live trace stream, pause/resume or cancel. |
|
||||
| `/run/[id]/report` | Reading Room: the report with page-anchored citations — hover or focus any `[n]` marker for the exact quoted passage — plus the citation ledger and Markdown/JSON exports. |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@researchx/frontend",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.8",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@12.6.0",
|
||||
"scripts": {
|
||||
|
||||
@@ -6,14 +6,16 @@
|
||||
* fills the question box (editable, NO auto-submit)
|
||||
* - Refine: disabled without a draft; the exchange completes and
|
||||
* "Use this" fills question + context; "Keep original"/Escape leaves
|
||||
* the draft untouched; Enter advances the exchange without
|
||||
* submitting the outer form; focus returns to the trigger on close
|
||||
* the draft untouched; Enter advances the exchange exactly once and
|
||||
* never submits the outer form; Enter on drawer buttons activates
|
||||
* them normally; focus returns to the trigger on close
|
||||
*/
|
||||
|
||||
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { AskView } from "@/components/ask-view";
|
||||
import { api } from "@/lib/api/client";
|
||||
import { clearAskState, getAskState } from "@/lib/ask-store";
|
||||
|
||||
const pushMock = vi.hoisted(() => vi.fn());
|
||||
@@ -261,4 +263,57 @@ describe("AskView", () => {
|
||||
// The keyboard user is not stranded on <body>.
|
||||
expect(refine).toHaveFocus();
|
||||
});
|
||||
|
||||
it("refine: Enter finishing the exchange composes exactly once", async () => {
|
||||
// Regression (P8 review): the section-level Enter guard re-fired
|
||||
// submitAnswer on the same bubbled event (stale stage closure) —
|
||||
// the composer ran twice, a doubled paid POST once v0.2 wires the
|
||||
// model. The input guard alone must own the Enter path.
|
||||
const composeSpy = vi.spyOn(api, "composeRefinedQuestion");
|
||||
const user = userEvent.setup();
|
||||
render(<AskView />);
|
||||
|
||||
await user.type(
|
||||
screen.getByLabelText(/what do you want to know\?/i),
|
||||
"What are the latest advances in solid-state batteries",
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: /Refine question/i }));
|
||||
await screen.findByRole("region", { name: /Refine your question/i });
|
||||
|
||||
const answer = await screen.findByLabelText(
|
||||
/What specific part of this topic matters most to you\?/i,
|
||||
);
|
||||
await user.type(answer, "automotive applications");
|
||||
await user.keyboard("{Enter}");
|
||||
await user.click(await screen.findByRole("button", { name: "Skip" }));
|
||||
await user.click(await screen.findByRole("button", { name: "Compose question" }));
|
||||
|
||||
await screen.findByText(/Refined question/i);
|
||||
expect(composeSpy).toHaveBeenCalledTimes(1);
|
||||
composeSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("refine: Enter on a drawer button activates it (Keep original closes)", async () => {
|
||||
// Regression (P8 review): the section-level Enter guard swallowed
|
||||
// the focused button's own activation during the questions stage —
|
||||
// keyboard "Keep original" did nothing.
|
||||
const user = userEvent.setup();
|
||||
render(<AskView />);
|
||||
|
||||
await user.type(
|
||||
screen.getByLabelText(/what do you want to know\?/i),
|
||||
"How good are solid-state batteries",
|
||||
);
|
||||
await user.click(screen.getByRole("button", { name: /Refine question/i }));
|
||||
await screen.findByRole("region", { name: /Refine your question/i });
|
||||
|
||||
screen.getByRole("button", { name: "Keep original" }).focus();
|
||||
await user.keyboard("{Enter}");
|
||||
expect(
|
||||
screen.queryByRole("region", { name: /Refine your question/i }),
|
||||
).toBeNull();
|
||||
expect(screen.getByLabelText(/what do you want to know\?/i)).toHaveValue(
|
||||
"How good are solid-state batteries",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -8,10 +8,11 @@
|
||||
* one clarifying question at a time, then the composed refined
|
||||
* question + suggested context. "Use this" applies the composition to
|
||||
* the form; "Keep original" discards it; the draft is never modified
|
||||
* until the user accepts. Enter advances the exchange (never the outer
|
||||
* form), and focus returns to the trigger when the drawer closes.
|
||||
* Scripted in the prototype — honest caption says the model-driven
|
||||
* version lands in v0.2.
|
||||
* until the user accepts. The answer input's Enter guard advances the
|
||||
* exchange (never the outer form — the input is the only Enter path,
|
||||
* and all drawer buttons are type="button"); focus returns to the
|
||||
* trigger when the drawer closes. Scripted in the prototype — honest
|
||||
* caption says the model-driven version lands in v0.2.
|
||||
*/
|
||||
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
@@ -157,12 +158,6 @@ function RefineDrawer({
|
||||
<section
|
||||
aria-label="Refine your question"
|
||||
className="mt-3 rounded-card border border-hairline bg-accent-wash px-5 py-5"
|
||||
onKeyDown={(event) => {
|
||||
if (event.key === "Enter" && exchange?.stage === "questions") {
|
||||
event.preventDefault();
|
||||
submitAnswer();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<h3 className="text-small font-semibold uppercase tracking-wide text-accent-ink">
|
||||
@@ -185,6 +180,17 @@ function RefineDrawer({
|
||||
Refine is unavailable right now — try again.
|
||||
</p>
|
||||
)}
|
||||
{error && (
|
||||
<div className="mt-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
className="rounded-pill border border-hairline px-4 py-1.5 text-caption font-medium text-ink-soft transition-colors hover:text-ink"
|
||||
>
|
||||
Keep original
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{exchange === null && !error && (
|
||||
<p className="mt-3 text-caption text-ink-faint">Preparing questions…</p>
|
||||
|
||||
@@ -107,6 +107,6 @@ describe("SettingsView (REQ-65)", () => {
|
||||
expect(screen.getByText("preview", { exact: true })).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText(/About & version|About and version/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/v0\.1\.6 · prototype/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/v0\.1\.8 · prototype/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -244,7 +244,7 @@ export function SettingsView() {
|
||||
visited-only citations, three clicks to a trustworthy report.
|
||||
</p>
|
||||
<p className="mt-2 text-caption text-ink-faint">
|
||||
v0.1.6 · prototype — runs on scripted mock data; the engine,
|
||||
v0.1.8 · prototype — runs on scripted mock data; the engine,
|
||||
adapters, and trust loop arrive in v0.2.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user