ship: final review + audit + approval-gate prep (P4) #4

Merged
coreci-bot merged 2 commits from phase/04-final-review-approval-ship into milestone/v0.1-prototype 2026-09-27 02:23:44 +00:00
13 changed files with 123 additions and 64 deletions
+4 -4
View File
@@ -1,8 +1,8 @@
{
"phase": 3,
"stage": "complete",
"phase": 4,
"stage": "verify",
"milestone": "v0.1",
"phase_role": "execution",
"phase_role": "final",
"attempts": 0,
"updated_at": "2026-09-27T02:10:00Z"
"updated_at": "2026-09-27T02:30:00Z"
}
+30 -44
View File
@@ -1,64 +1,50 @@
# PLAN — Milestone v0.1 Phase 3: v2 Surface Mockups + Polish + 3-Click Validation
# PLAN — Milestone v0.1 Phase 4 (FINAL): Review + Owner Approval Gate + Ship
---
phase: 3
phase: 4
milestone: v0.1
status: planned
status: executed
must_haves:
- "Project graph mockup panel: cross-linked sources and findings across projects, rendered from mock data (REQ-60)"
- "Task inbox mockup panel: open questions become checkable follow-ups, rendered from mock data (REQ-60)"
- "Typography care pass: reader experience per vision §4 — report body measure/leading, section rhythm, small-caps/labels audit across all screens (REQ-61)"
- "≤3-click happy path validated on defaults: Ask → Choose → Run initiated in 3 clicks, verified by test (REQ-61)"
- "Full-milestone review: PASS-WITH-FIXES verdict — all 5 P0s fixed and verified (drawer separators, empty-question guard, border-hairline token, fixture URL honesty, README accuracy)"
- "Audit: PASS — reconstruction test, file discipline, branch hygiene, commit discipline, tag verification, critical-issue check all green"
- "Final phase patch v0.1.4 shipped (pending owner approval for milestone merge to main)"
personas:
- frontend-engineer
requirements: [REQ-60, REQ-61]
- ci-lead-developer
requirements: [REQ-62]
---
## User-Facing Surface
- **`/library`** — the v2-surface mockup page: two panels rendered from fixture data — (1) **project graph**: nodes for research topics/projects with cross-links to shared sources and findings (visual mockup: SVG or CSS positioned nodes with labeled edges, clearly marked "v2 preview"); (2) **task inbox**: open questions extracted from past runs as checkable follow-up items with status toggles. A page-level banner explains these are v2 vision mockups, not yet functional.
- All existing screens get the typography care pass: consistent section rhythm, reading measure enforced on long-form text, caption/label treatment unified.
- Navigation affordance: the Reading Room and run screens gain a "Library (v2 preview)" link so the mockup page is discoverable in the demo.
The prototype deliverable presented for owner approval: the full 5-screen click-path (Ask → Choose → Plan & Run → Reading Room → Library preview), README quickstart, releases v0.1.0–v0.1.3, and this phase's review-driven polish (P0 fixes) as v0.1.4-pending.
## Happy Path
## Happy Path (owner review script)
1. Owner runs the full P1+P2 path (ask → choose → approve → trace → report)
2. From the report screen, clicks "Library (v2 preview)" → sees project graph with 4–6 topic nodes sharing source edges + task inbox with 3–5 checkable items
3. Toggles a task's checkbox → item visibly flips status (client state only)
4. Back on /ask: with defaults (no context, Deep Report pre-recommended), the owner reaches a streaming run in exactly 3 clicks: Research this → (arrive /choose) Start research → (arrive /run) Approve plan & start research
5. The 3-click criterion is pinned by an automated test, not just manual belief
1. `cd frontend && pnpm install && pnpm dev` → http://localhost:3000
2. Ask → question → Research this (click 1)
3. Choose → Deep Report recommended card → Start research (click 2)
4. Plan & Run → Approve plan (click 3) → live trace → pause/resume/cancel
5. Read the report → hover [2] → exact quote; ledger panel; export Markdown/JSON
6. Library (v2 preview) → graph + inbox
7. Owner decision: approve → v0.1.4 + milestone merge to main; or request changes
## What this phase did
1. **Review** (multi-persona, full milestone diff `main..HEAD`): verdict PASS-WITH-FIXES — 5 P0s (all owner-visible) + 2 P1s + P2 notes. All P0s fixed in-phase: dials separator rendering (P1-1 flag from P1), empty-question guard (P1-2 flag from P1), border-hairline Tailwind token (21 surfaces were near-black), fixture URL switched from a real arXiv link (resolving to an unrelated paper) to example.org, README screens table rewritten for all 5 routes. P1s: report title hardcodes Deep Report shape (documented prototype limitation), getRun fallback lacks a demo-run caption (documented). P2 notes logged for v0.2 hardening.
2. **Audit**: AUDIT PASS — tags v0.1.0–v0.1.3 verified annotated + on milestone branch; REQ-52..61 spot-checked as genuinely delivered; secrets never committed; branch hygiene clean; commit discipline PASS (2 zero-loss deviations noted); gates re-run green 42/42.
3. **Ship (this commit sequence)**: PR #4 (phase/04 → milestone), squash-merge, tag v0.1.4, release with full milestone summary. **Milestone merge to main happens ONLY after recorded owner approval (REQ-62)** — the pipeline halts at the gate.
## UX Acceptance Criteria
- Graph: nodes labeled with topic names; edges show shared-source counts; hover on node highlights it (title attribute acceptable); visually distinct from production screens ("v2 preview" banner + muted styling)
- Inbox: items show question text + originating run status; checkboxes toggle open↔done; at least one item pre-checked
- Typography: body text ≥16px, reading measure ≤68ch on report/ask/choose long-form, consistent heading scale, no clipped or truncated labels
- 3-click test: automated test counts discrete user clicks from /ask to a streaming run using default methodology — must be ≤3
- Quality gates: `pnpm typecheck && pnpm lint && pnpm test && pnpm build` green
## Tasks (wave-ordered; persona: frontend-engineer unless noted)
### Wave 1 — Fixtures
- **T-3.1**: Library fixtures in mock layer: `getProjectGraph()` (nodes: topic/project + linked sources + findings) and `getTaskInbox()` (items: question, source run, status) — extend ApiClient interface (both map cleanly to v0.2 GET /api/graph + GET /api/tasks) (REQ-60)
### Wave 2 — Library page
- **T-3.2**: `/library` route with v2-preview banner; ProjectGraph panel (SVG/CSS nodes + labeled edges from fixtures); TaskInbox panel with toggleable checkboxes (client state) (REQ-60)
- **T-3.3**: Navigation links: Reading Room + run screens gain "Library (v2 preview)" link; root nav hint if layout has one (REQ-60)
### Wave 3 — Typography pass
- **T-3.4**: Audit + fix typography across screens: reading measure on long-form, section rhythm (spacing-section usage), caption/label letter-spacing consistency, heading scale order (display → headline → body) (REQ-61)
### Wave 4 — 3-click validation
- **T-3.5**: Automated 3-click test: /ask → Research this (1) → /choose → Start research on recommended default (2) → /run → Approve plan (3) → assert trace streaming; document the count in the test name (REQ-61)
### Wave 5 — Gate (persona: ci-lead-developer)
- **T-3.6**: Boundary review + gate run; P1-flag fixes deferred to P4 are NOT pulled forward (P3 stays scoped)
- Owner demo script runs clean on the shipped code
- No placeholder/stub text in owner-visible surfaces
- The 3-click criterion still pinned green post-fixes
## Verification
- Gates green; happy path walk passes; REQ-60, REQ-61 covered
- 3-click criterion pinned by test
- Gates: typecheck ✓ lint ✓ tests 42/42 ✓ build ✓ (re-run after P0 fixes, plus by audit)
- border-hairline utility verified present in compiled production CSS
- Review P0 worklist: 5/5 closed with tests where feasible (dials unit test, empty-question guard test)
## Scope Guard
P3 only. The P4 final phase handles: review, audit, P1 flags (P1-1 drawer hyphen rendering from P1, P1-2 empty-question guard from P1, P2's routed P2 notes), owner approval gate, milestone merge. No v0.2 work.
Milestone merge to main, v0.1.4 release 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.
+1 -1
View File
@@ -38,7 +38,7 @@ Two milestones, owner-directed 2026-09-27. **Milestone v0.1 — Prototype**: the
- **Requirements covered**: milestone gate (REQ-52..61 approved)
- **Success criteria**: owner approval recorded; v0.1.4 tagged; milestone branches deleted per ship workflow
- **Depends on**: 3
- **Status**: not_started
- **Status**: in_progress (review PASS-WITH-FIXES → 5 P0s fixed; audit PASS; awaiting owner approval)
## Milestone v0.2 — MVP (feature) — BLOCKED until prototype approval
+3 -1
View File
@@ -22,7 +22,9 @@ Three clicks from cold start to initiated research.
|---|---|
| `/ask` | One question box + one optional context box. Nothing else competing for attention. |
| `/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]` | Placeholder — Plan & Run ships in phase 2. |
| `/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. |
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.
+3 -3
View File
@@ -61,7 +61,7 @@ The happy path:
```
src/
app/ App Router routes (ask, choose, run/[id], run/[id]/report)
app/ App Router routes (ask, choose, run/[id], run/[id]/report, library)
components/ UI components (server by default, client where interactive)
lib/ Domain types, ApiClient boundary, mock data, stores
test/ Test harness setup
@@ -71,5 +71,5 @@ Prototype scope note: ETAs, cost estimates, and the scripted run are
realistic fixture data — not live research. The trace stream replays a
deterministic Deep Report run with pause/resume/cancel controls; the
Reading Room's report, ledger, and exports are generated from the same
fixture. The v2 surface mockups (project graph, task inbox) ship in the
next phase.
fixture. The `/library` project graph and task inbox are v2 preview
mockups behind the same ApiClient boundary.
+1
View File
@@ -50,6 +50,7 @@
--color-ink-soft: #4a4945; /* secondary text */
--color-ink-faint: #8a8880; /* muted metadata */
--color-line: #e5e2da; /* hairline borders */
--color-hairline: #e5e2da; /* border-hairline utility */
--color-accent: #1e5c3f; /* deep green — primary action */
--color-accent-strong: #164732; /* hover / pressed */
@@ -116,4 +116,26 @@ describe("ChooseView", () => {
expect(runRoute).toMatch(/^\/run\/run-[a-z0-9]+$/);
});
});
it("empty-question guard: Start research disabled without a question (P1-2 fix)", async () => {
const methodologies = await loadFixtures();
const deepReport = methodologies.find((m) => m.slug === "deep-report");
if (!deepReport) throw new Error("fixture missing deep-report");
// Direct /choose navigation or a hard refresh: no carried question.
setAskState({ question: "" });
render(
<MethodologyCard methodology={deepReport} question={""} />,
);
const start = screen.getByRole("button", { name: "Start research" });
expect(start).toBeDisabled();
expect(start).toHaveAttribute("aria-disabled", "true");
// Whitespace-only questions are equally blocked.
render(
<MethodologyCard methodology={deepReport} question={" "} />,
);
expect(screen.getAllByRole("button", { name: "Start research" })[0]).toBeDisabled();
});
});
+7 -1
View File
@@ -128,7 +128,13 @@ export function MethodologyCard({
<button
type="button"
onClick={handleStart}
disabled={starting}
disabled={starting || question.trim() === ""}
aria-disabled={question.trim() === ""}
title={
question.trim() === ""
? "Ask a question first — the Ask screen is one click away"
: undefined
}
className="w-full rounded-pill bg-accent px-5 py-2.5 text-small font-medium text-paper-raised transition-colors hover:bg-accent-strong disabled:cursor-not-allowed disabled:opacity-50"
>
{starting ? "Starting…" : "Start research"}
@@ -85,7 +85,7 @@ describe("ReadingRoom", () => {
expect(tooltip.textContent).toContain(
"Sulfide electrolytes exhibit ionic conductivity comparable to liquid electrolytes",
);
expect(tooltip.textContent).toContain("arxiv.org");
expect(tooltip.textContent).toContain("example.org");
expect(tooltip.textContent).toContain("tier 1");
});
@@ -95,7 +95,7 @@ describe("ReadingRoom", () => {
expect(markdown.filename).toBe(`researchx-${id}.md`);
expect(markdown.content).toContain("## Sources");
expect(markdown.content).toMatch(/\[.*\]\(https:\/\/.+\)/);
expect(markdown.content).toContain("](https://arxiv.org/abs/2401.00001)");
expect(markdown.content).toContain("](https://example.org/arxiv-sulfide-electrolytes-review)");
const json = await api.exportReport(id, "json");
expect(json.filename).toBe(`researchx-${id}.json`);
@@ -128,7 +128,7 @@ describe("MockApiClient (bound as `api`)", () => {
// Per-claim markers survive as footnote-style references…
expect(markdown.content).toContain("[^p1]:");
// …and each citation is a real Markdown link to its source URL.
expect(markdown.content).toContain("](https://arxiv.org/abs/2401.00001)");
expect(markdown.content).toContain("](https://example.org/arxiv-sulfide-electrolytes-review)");
expect(markdown.content).toContain("✓ live");
expect(markdown.content).toContain("△ stale");
});
+3 -3
View File
@@ -36,9 +36,9 @@ export const scriptedCitations: readonly Citation[] = [
},
{
id: "c2",
url: "https://arxiv.org/abs/2401.00001",
url: "https://example.org/arxiv-sulfide-electrolytes-review",
title: "Sulfide Electrolytes for All-Solid-State Lithium Batteries: A Review",
site: "arxiv.org",
site: "example.org",
passageId: "p2",
quote:
"Sulfide electrolytes exhibit ionic conductivity comparable to liquid electrolytes, but interface degradation during cycling remains the central obstacle to commercial cycle life.",
@@ -161,7 +161,7 @@ export function scriptedTraceEvents(runId: string): TraceEvent[] {
type: "source_visited",
message:
"Visited: Sulfide Electrolytes for All-Solid-State Lithium Batteries: A Review",
detail: "arxiv.org · authority tier 1",
detail: "example.org · authority tier 1",
},
{
id: `${runId}-e09`,
+35
View File
@@ -0,0 +1,35 @@
/**
* Dial formatting tests (final-phase review fix — P0-1).
*
* formatDialValue must preserve separator meaning: hyphens stay
* hyphens ("per-claim" → "Per-Claim"), plus stays plus
* ("web+academic" → "Web+Academic"). This survived three phases
* unpinned; it is pinned now.
*/
import { describe, expect, it } from "vitest";
import { formatDialValue } from "@/lib/dials";
describe("formatDialValue", () => {
it("numbers pass through as strings", () => {
expect(formatDialValue(1)).toBe("1");
expect(formatDialValue(3)).toBe("3");
});
it("preserves hyphens: per-claim renders Per-Claim, not Per+Claim", () => {
expect(formatDialValue("per-claim")).toBe("Per-Claim");
expect(formatDialValue("plan-only")).toBe("Plan-Only");
expect(formatDialValue("plan+draft")).toBe("Plan+Draft");
});
it("preserves plus separators: web+academic renders Web+Academic", () => {
expect(formatDialValue("web+academic")).toBe("Web+Academic");
});
it("single words title-case; connectors stay lowercase", () => {
expect(formatDialValue("encyclopedic")).toBe("Encyclopedic");
// Space-separated words beyond connectors title-case normally;
// the connector rule keeps "and"/"or" lowercase mid-compound.
expect(formatDialValue("breadth and depth")).toBe("Breadth and depth");
});
});
+11 -4
View File
@@ -9,14 +9,21 @@ import { DIALS } from "@/lib/types";
/** Humanize a raw dial value: numbers pass through, strings title-case. */
export function formatDialValue(value: DialValues[keyof DialValues]): string {
if (typeof value === "number") return String(value);
// Split into words AND separators, title-case words, keep the
// original separator characters ("per-claim" → "Per-Claim",
// "web+academic" → "Web+Academic") — separators are meaningful.
return value
.split(/[-+]/)
.split(/([-+])/)
.map((part) =>
part === "and" || part === "or"
part === "-" || part === "+"
? part
: part.charAt(0).toUpperCase() + part.slice(1),
: part === "and" || part === "or"
? part
: part === ""
? part
: part.charAt(0).toUpperCase() + part.slice(1),
)
.join("+");
.join("");
}
/** All 12 dial definitions (for the Advanced drawer). */