diff --git a/.ciagent/ARCHITECTURE.md b/.ciagent/ARCHITECTURE.md index 71dd724..e7396a3 100644 --- a/.ciagent/ARCHITECTURE.md +++ b/.ciagent/ARCHITECTURE.md @@ -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) diff --git a/.ciagent/CHECKPOINT.json b/.ciagent/CHECKPOINT.json index e64d8a2..5498728 100644 --- a/.ciagent/CHECKPOINT.json +++ b/.ciagent/CHECKPOINT.json @@ -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" +} \ No newline at end of file diff --git a/.ciagent/PLAN.md b/.ciagent/PLAN.md index a0ce778..12fabf1 100644 --- a/.ciagent/PLAN.md +++ b/.ciagent/PLAN.md @@ -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). \ No newline at end of file +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. \ No newline at end of file diff --git a/.ciagent/release/VERSIONING.md b/.ciagent/release/VERSIONING.md index 4973932..ab7d5d1 100644 --- a/.ciagent/release/VERSIONING.md +++ b/.ciagent/release/VERSIONING.md @@ -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 diff --git a/README.md b/README.md index 98a3da7..5149a51 100644 --- a/README.md +++ b/README.md @@ -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. | diff --git a/frontend/package.json b/frontend/package.json index 8d80a26..cd11077 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "@researchx/frontend", - "version": "0.1.0", + "version": "0.1.8", "private": true, "packageManager": "pnpm@12.6.0", "scripts": { diff --git a/frontend/src/components/ask-view.test.tsx b/frontend/src/components/ask-view.test.tsx index 6a9fb3e..9d53eb8 100644 --- a/frontend/src/components/ask-view.test.tsx +++ b/frontend/src/components/ask-view.test.tsx @@ -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 . 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(); + + 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(); + + 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", + ); + }); }); \ No newline at end of file diff --git a/frontend/src/components/refine-question.tsx b/frontend/src/components/refine-question.tsx index 734a742..11e1b98 100644 --- a/frontend/src/components/refine-question.tsx +++ b/frontend/src/components/refine-question.tsx @@ -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({
{ - if (event.key === "Enter" && exchange?.stage === "questions") { - event.preventDefault(); - submitAnswer(); - } - }} >

@@ -185,6 +180,17 @@ function RefineDrawer({ Refine is unavailable right now — try again.

)} + {error && ( +
+ +
+ )} {exchange === null && !error && (

Preparing questions…

diff --git a/frontend/src/components/settings-view.test.tsx b/frontend/src/components/settings-view.test.tsx index d637249..3ec5f48 100644 --- a/frontend/src/components/settings-view.test.tsx +++ b/frontend/src/components/settings-view.test.tsx @@ -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(); }); -}); \ No newline at end of file +}); diff --git a/frontend/src/components/settings-view.tsx b/frontend/src/components/settings-view.tsx index 9ca1f78..02dc80d 100644 --- a/frontend/src/components/settings-view.tsx +++ b/frontend/src/components/settings-view.tsx @@ -244,7 +244,7 @@ export function SettingsView() { visited-only citations, three clicks to a trustworthy report.

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