From 3242fe9cb0789388fd1a734094752a56b1ad70ad Mon Sep 17 00:00:00 2001 From: coreci Date: Sun, 27 Sep 2026 04:27:08 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(P06):=20final-phase=20review=20?= =?UTF-8?q?=E2=80=94=20deep-link=20resolution=20P0=20+=205=20P1s=20fixed?= =?UTF-8?q?=20with=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ---ci--- phase: 6 milestone: v0.1 status: execute persona: ci-lead-developer requirements: covered: [REQ-62] partial: [REQ-63, REQ-64, REQ-65, REQ-66] ---ci--- Full-milestone review verdict: PASS-WITH-FIXES. P0-1 (owner-visible, demo-path): stored-report deep links resolved to the wrong run — every library card and palette entry opened the demo report under a mismatched run id. Fixed: #storedRun resolver in mock-client getRun + card-title override in getReport/ exportReport + parameterized builders in scripted-run. Probe test proves run/report/ledger/markdown-export agree; demo fallback for unknown ids unchanged. New boundary test: every stored report deep-links to its own id/question/methodology/title. P1s fixed in-phase: - P1-1 avatar menu: mousedown-dismiss vs click-toggle race kept it open — toggleRef guard + forwarded ref; toggle-close test added - P1-4 lost preview labels: project-graph + task-inbox panels now carry honest preview pills; stale 'Library (v2 preview)' links corrected in reading-room + run-view - P1-5 root README: shell/settings/library surfaces documented - P1-6 settings: stale state after save; dial display now previews the LIVE methodology selection with formatted values - P1-2/P1-3 doc rot: run-page header comment, VERSIONING.md tag table (v0.1.5, v0.1.6 gate) Suite 72 → 75. Gates re-verified independently: typecheck, lint 0/0, test 75/75, build 8 routes. Post-hoc v0.2 log: demo-run fallback 404 semantics, shared scripted body disclosure, palette scroll- into-view, raw methodology slugs on cards, pt-[14vh] token. --- .ciagent/PLAN.md | 2 +- .ciagent/REQUIREMENTS.md | 2 +- .ciagent/ROADMAP.md | 3 +- .ciagent/release/VERSIONING.md | 5 ++- README.md | 7 +++- frontend/src/app/ask/page.tsx | 2 +- frontend/src/app/choose/page.tsx | 2 +- frontend/src/app/layout.tsx | 2 +- frontend/src/app/library/page.tsx | 2 +- frontend/src/app/run/[id]/page.tsx | 11 +++--- frontend/src/app/run/[id]/report/page.tsx | 2 +- frontend/src/app/settings/page.tsx | 3 +- frontend/src/components/app-shell.test.tsx | 10 +++++ frontend/src/components/app-top-bar.tsx | 20 ++++++---- frontend/src/components/library-view.tsx | 28 +++++++++++--- frontend/src/components/reading-room.tsx | 2 +- frontend/src/components/run-view.tsx | 2 +- .../src/components/settings-view.test.tsx | 19 +++++++++- frontend/src/components/settings-view.tsx | 37 +++++++++++++------ frontend/src/components/user-menu.tsx | 11 ++++-- frontend/src/lib/api/mock/mock-client.ts | 35 +++++++++++++++++- frontend/src/lib/api/mock/scripted-run.ts | 15 ++++++-- .../src/lib/api/mock/shell-boundary.test.ts | 16 ++++++++ frontend/src/lib/types.ts | 3 +- 24 files changed, 184 insertions(+), 57 deletions(-) diff --git a/.ciagent/PLAN.md b/.ciagent/PLAN.md index 8f2da8a..349659d 100644 --- a/.ciagent/PLAN.md +++ b/.ciagent/PLAN.md @@ -50,4 +50,4 @@ Five routes gain a persistent shell; two new surfaces ship: ## Scope Guard -P6 (final review + approval re-presentation) follows P5 ship. 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 auth/multi-tenancy (local single-user profile only). \ No newline at end of file +P6 (final review + approval re-presentation) follows P5 ship. 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 auth/multi-tenancy (local single-user profile only). diff --git a/.ciagent/REQUIREMENTS.md b/.ciagent/REQUIREMENTS.md index 3581b7b..1f99df9 100644 --- a/.ciagent/REQUIREMENTS.md +++ b/.ciagent/REQUIREMENTS.md @@ -122,4 +122,4 @@ | REQ-25, REQ-26, REQ-33 (API, client, tooling) | v0.2 | P3 | blocked | | REQ-17, REQ-32, REQ-34 (adversarial, E2E, round-trip) | v0.2 | P4 | blocked | | All REQ-01..34 (milestone gate) | v0.2 | P5 | blocked | -| REQ-35..51 | v2 backlog (post-MVP milestones) | — | pending | \ No newline at end of file +| REQ-35..51 | v2 backlog (post-MVP milestones) | — | pending | diff --git a/.ciagent/ROADMAP.md b/.ciagent/ROADMAP.md index dc19479..aee1019 100644 --- a/.ciagent/ROADMAP.md +++ b/.ciagent/ROADMAP.md @@ -92,4 +92,5 @@ Phases renumbered from the original v1.0 plan (1-based within the milestone): - **v0.1.6 = milestone release** (P6 final phase patch — the approved extended prototype release, pending owner decision) - **v0.2.x = MVP release** (MAJOR still 0; ship only if all v0.2 phases complete) - If MVP scope extends: additional 0.x minor milestones (v0.3, …), each a full milestone cycle -- **v1.0.0**: owner-approved release only — not created by the pipeline autonomously \ No newline at end of file +- **v1.0.0**: owner-approved release only — not created by the pipeline autonomously + diff --git a/.ciagent/release/VERSIONING.md b/.ciagent/release/VERSIONING.md index 5919c6a..4973932 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.4 | `bash .ciagent/release/deploy.sh v0.1.4` | +| v0.1 prototype | v0.1.0 … v0.1.5 | `bash .ciagent/release/deploy.sh v0.1.5` | | v0.2 MVP | v0.2.0 … | `bash .ciagent/release/deploy.sh v0.2.x` | ## Rules @@ -32,7 +32,8 @@ as established by the CIAgent branch strategy and `config.json`. `deploy.sh`'s parameter block. 6. **Milestone gating applies to deployment too.** The v0.1 prototype deploys for owner review. v0.2 releases deploy only after the - v0.1.4 owner-approval gate is cleared. + REQ-62 owner-approval gate is cleared (recorded on the extended + prototype, v0.1.6). ## Container conventions diff --git a/README.md b/README.md index da9be12..98a3da7 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ pnpm install pnpm dev ``` -Open http://localhost:3000 — you land on **Ask**. Type a question, click **Research this**, pick a methodology on **Choose** (Deep Report is the recommended default), and press **Start research**. +Open http://localhost:3000 — you land on **Ask**, inside the app shell (left sidebar, top bar with ⌘K search and your avatar). Type a question, click **Research this**, pick a methodology on **Choose** (Deep Report is the recommended default), and press **Start research**. Three clicks from cold start to initiated research. @@ -24,7 +24,10 @@ Three clicks from cold start to initiated research. | `/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. | -| `/library` | v2 preview: project graph and task inbox mockups rendered from fixture data. | +| `/library` | Stored reports filed into auto-categorized folders (sortable, links into the Reading Room), the Connections panel (shared sources ⇄, related topics →), plus v2-preview project graph and task inbox. | +| `/settings` | API-key status (masked, never raw), the reports storage directory, methodology defaults with the resolved 12 dials, appearance, and about/version. | + +The persistent shell (every screen): sidebar nav + adapter connection-status dots, top bar with the ⌘K navigation palette (screens + stored reports) and the avatar menu (local profile, Settings, About). All data flows through a typed `ApiClient` interface backed by `MockApiClient` — the same components will talk to the FastAPI backend via the OpenAPI-generated client in the MVP milestone, with zero rewrites. diff --git a/frontend/src/app/ask/page.tsx b/frontend/src/app/ask/page.tsx index a5e8ccd..87e77e7 100644 --- a/frontend/src/app/ask/page.tsx +++ b/frontend/src/app/ask/page.tsx @@ -28,4 +28,4 @@ export default function AskPage() {

); -} \ No newline at end of file +} diff --git a/frontend/src/app/choose/page.tsx b/frontend/src/app/choose/page.tsx index 31eda5e..1fdd687 100644 --- a/frontend/src/app/choose/page.tsx +++ b/frontend/src/app/choose/page.tsx @@ -11,4 +11,4 @@ import { api } from "@/lib/api/client"; export default async function ChoosePage() { const methodologies = await api.getMethodologies(); return ; -} \ No newline at end of file +} diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx index dfd50f8..907c97e 100644 --- a/frontend/src/app/layout.tsx +++ b/frontend/src/app/layout.tsx @@ -36,4 +36,4 @@ export default function RootLayout({ ); -} \ No newline at end of file +} diff --git a/frontend/src/app/library/page.tsx b/frontend/src/app/library/page.tsx index c5172cc..20215fc 100644 --- a/frontend/src/app/library/page.tsx +++ b/frontend/src/app/library/page.tsx @@ -11,4 +11,4 @@ export default function LibraryPage() { ); -} \ No newline at end of file +} diff --git a/frontend/src/app/run/[id]/page.tsx b/frontend/src/app/run/[id]/page.tsx index a50ad21..005d141 100644 --- a/frontend/src/app/run/[id]/page.tsx +++ b/frontend/src/app/run/[id]/page.tsx @@ -1,12 +1,11 @@ import { RunView } from "@/components/run-view"; /** - * /run/[id] (T-3.2b) — placeholder for the Plan & Run screen. + * /run/[id] (T-2.4, REQ-57) — the Plan & Run screen. * - * Phase 2 ships the real screen (plan approval, live scripted trace, - * pause/cancel). For now: the run's question + methodology and a clear - * "ships in phase 2" notice, so the Start-research click-path lands - * somewhere honest. + * Server shell for the run stage: plan approval, the live scripted + * trace, and the terminal-state onward links. Run data flows through + * the ApiClient boundary inside RunView. */ export default async function RunPage({ params, @@ -19,4 +18,4 @@ export default async function RunPage({ ); -} \ No newline at end of file +} diff --git a/frontend/src/app/run/[id]/report/page.tsx b/frontend/src/app/run/[id]/report/page.tsx index 265c145..e79c88a 100644 --- a/frontend/src/app/run/[id]/report/page.tsx +++ b/frontend/src/app/run/[id]/report/page.tsx @@ -11,4 +11,4 @@ export default async function ReportPage({ ); -} \ No newline at end of file +} diff --git a/frontend/src/app/settings/page.tsx b/frontend/src/app/settings/page.tsx index 87d0067..f148b52 100644 --- a/frontend/src/app/settings/page.tsx +++ b/frontend/src/app/settings/page.tsx @@ -11,4 +11,5 @@ export default function SettingsPage() { ); -} \ No newline at end of file +} + diff --git a/frontend/src/components/app-shell.test.tsx b/frontend/src/components/app-shell.test.tsx index db91008..8d1e72c 100644 --- a/frontend/src/components/app-shell.test.tsx +++ b/frontend/src/components/app-shell.test.tsx @@ -115,6 +115,16 @@ describe("AppTopBar (REQ-63, REQ-66)", () => { await user.keyboard("{Escape}"); expect(screen.queryByRole("menu", { name: "User menu" })).toBeNull(); }); + + it("clicking the avatar again closes the menu (toggle, not reopen)", async () => { + const user = userEvent.setup(); + render(); + const avatar = screen.getByRole("button", { name: "User menu" }); + await user.click(avatar); + await screen.findByRole("menu", { name: "User menu" }); + await user.click(avatar); + expect(screen.queryByRole("menu", { name: "User menu" })).toBeNull(); + }); }); describe("CommandPalette (REQ-63)", () => { diff --git a/frontend/src/components/app-top-bar.tsx b/frontend/src/components/app-top-bar.tsx index aa946ea..54d29e6 100644 --- a/frontend/src/components/app-top-bar.tsx +++ b/frontend/src/components/app-top-bar.tsx @@ -11,7 +11,7 @@ */ import { usePathname, useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { forwardRef, useEffect, useRef, useState } from "react"; import { api, type ReportLibrary, @@ -26,6 +26,7 @@ export function AppTopBar() { const [paletteOpen, setPaletteOpen] = useState(false); const [menuOpen, setMenuOpen] = useState(false); const [library, setLibrary] = useState(null); + const avatarRef = useRef(null); // ⌘K / Ctrl-K opens the palette from anywhere in the app. useEffect(() => { @@ -86,11 +87,16 @@ export function AppTopBar() { - setMenuOpen((v) => !v)} /> + setMenuOpen((v) => !v)} + /> {menuOpen && ( setMenuOpen(false)} onDismiss={() => setMenuOpen(false)} /> @@ -111,13 +117,10 @@ export function AppTopBar() { } /** The avatar disc — initials from the profile fixture. */ -function AvatarButton({ - menuOpen, - onToggle, -}: { +const AvatarButton = forwardRef void; -}) { +}>(function AvatarButton({ menuOpen, onToggle }, ref) { const [profile, setProfile] = useState(null); useEffect(() => { let cancelled = false; @@ -136,6 +139,7 @@ function AvatarButton({ return (