diff --git a/apps/desktop/e2e/live-backlog.spec.ts b/apps/desktop/e2e/live-backlog.spec.ts index 695ee6a..1da6fa4 100644 --- a/apps/desktop/e2e/live-backlog.spec.ts +++ b/apps/desktop/e2e/live-backlog.spec.ts @@ -45,6 +45,14 @@ test.describe('live backlog', () => { // fixture (which lists Beta / Pilot-ready / v1.0). await expect(win.getByText(/P2 — Scheduler/)).toBeVisible() await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-runway.png'), fullPage: true, animations: 'disabled' }) + + // Milestone drill-in — clicking a real milestone opens its real detail + await win.getByText(/P2 — Scheduler/).click() + await expect(win.getByRole('heading', { name: 'P2 — Scheduler + Monte Carlo' })).toBeVisible() + await expect(win.getByText(/\d+ issues · est \d+d/)).toBeVisible() + await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-milestone.png'), fullPage: true, animations: 'disabled' }) + await rail.getByRole('button', { name: 'Runway' }).click() + await win.getByRole('button', { name: 'Full report' }).click() await expect(win.getByText(/cold-start · \d+\/20|curve active · n ≥ 20/)).toBeVisible() await win.screenshot({ path: join(here, '.artifacts', 'screens', 'live-calibration.png'), fullPage: true, animations: 'disabled' }) diff --git a/apps/desktop/src/renderer/src/components/screens/milestone-screen.tsx b/apps/desktop/src/renderer/src/components/screens/milestone-screen.tsx index fd9930b..9348dbc 100644 --- a/apps/desktop/src/renderer/src/components/screens/milestone-screen.tsx +++ b/apps/desktop/src/renderer/src/components/screens/milestone-screen.tsx @@ -1,22 +1,44 @@ import React from 'react' import { COLUMNS, type IssueRef } from '../../data/fixtures.js' +import { type MilestoneView } from '../../lib/backlog.js' import { BurnUpCone } from '../charts/chart.js' import { Badge, Button, Card, Icon, Tag } from '../ui/index.js' -// Milestone detail — scope, cone, issues; forecasts stay ranges -export function MilestoneScreen({ onBack, onOpenIssue }: { onBack: () => void; onOpenIssue: (issue: IssueRef) => void }) { +// Milestone detail — scope, cone, issues; forecasts stay ranges. +// `data` (real milestone forecast) overrides the demo fixture when present. +export function MilestoneScreen({ + onBack, + onOpenIssue, + data, +}: { + onBack: () => void + onOpenIssue: (issue: IssueRef) => void + data?: MilestoneView +}) { const cols = COLUMNS const byState = (ids: number[]) => cols.flatMap((c) => c.issues.map((i) => ({ ...i, col: c.label }))).filter((i) => ids.includes(i.id)) - const groups = [ + const fixtureGroups = [ { label: 'Steeping', issues: byState([87, 84]) }, { label: 'In review', issues: byState([92]) }, { label: 'Queued', issues: byState([102, 103, 99, 96, 78]) }, { label: 'Done', issues: byState([71, 69, 65]), muted: true }, ] + // real or demo, in one shape the render loop understands + const groups = data + ? data.groups.map((g) => ({ label: g.label, issues: g.issues, muted: g.label === 'Done' })) + : fixtureGroups + const name = data ? data.name : 'Beta' + const dueLine = data + ? `milestone · due ${data.due} · ${data.soft ? 'soft — scope may flex' : 'hard deadline'}` + : 'milestone · due Mar 15 · soft — scope may flex' + const forecastLabel = data ? (data.forecastRange ? `80% ${data.forecastRange}` : 'all shipped') : '80% Mar 3–12' + const scopeStat = data ? `${data.scopeCount} issues · est ${data.scopeEstDays}d` : '42 issues · est 61d' + const doneStat = data ? `${data.doneCount} · ${data.donePct}%` : '24 · 57%' + const Stat = ({ label, value, tone }: { label: string; value: string; tone?: string }) => (
-
- Comfortably ahead. Beta needs #87 more than it needs my commentary. + {data + ? data.cone + ? `${data.scopeCount - data.doneCount} open of ${data.scopeCount}. Cone over what's left.` + : 'Everything here has shipped.' + : 'Comfortably ahead. Beta needs #87 more than it needs my commentary.'}