Ports the nine remaining design-handoff screens to typed TSX and wires them into the shell, retiring the placeholders for every primary view. - The pot (#17 / P3-4): BoardScreen 5-column kanban with live search + empty state, Tabs into GanttView (state bars, critical inset rules, dotted 80% tails, today rule) and DepsGraph (layered DAG, bezier edges, spruce critical path, milestone terminal). - Runway (#18 / P3-5): RunwayScreen (range bars via RunwayBar, capacity, calibration teaser), CalibrationScreen (scatter + honest diagonal + ×1.18 fit, per-label/per-person bias), MilestoneScreen (stats strip, cone, grouped issues). - Issue + Inbox (#19 / P3-6): IssueScreen (human intent left, machine- derived lifecycle/forecast/deps right, "writes as you" composer), InboxScreen (day-grouped, filter tabs, unread dots, mark-all-read). - Capture (#20 / P3-7): CaptureScreen dump→interview→review→filed state machine with running clock. Shell: openIssue now carries an IssueRef; adds readIds (inbox) + issue state; routes board/runway/calibration/milestone/issue/inbox/capture and the runway→calibration/milestone + issue back-stack. Focus/Standup pass IssueRefs. Select/Input gain defaultValue (Capture's editable tray). Fixtures mirrored from data.js. typecheck + 11 e2e green; all nine screens screenshot-verified in light. Closes P3-4, P3-5, P3-6, P3-7. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
100 lines
3.4 KiB
TypeScript
100 lines
3.4 KiB
TypeScript
import React from 'react'
|
||
|
||
import { FOCUS, type FocusIssue, type IssueRef, TODAY } from '../../data/fixtures.js'
|
||
import { BurnUpCone } from '../charts/chart.js'
|
||
import { Badge, Button, Card, IconButton, Tag } from '../ui/index.js'
|
||
|
||
/**
|
||
* Morning service — the Now/Next/Later focus cards + the milestone burn-up cone.
|
||
* Data is fixture (data.js) until P2's scheduler + Monte Carlo feed it.
|
||
*/
|
||
export function FocusScreen({ onOpenIssue }: { onOpenIssue: (issue: IssueRef) => void }) {
|
||
const FocusRow = ({ slot, issue, jade }: { slot: string; issue: FocusIssue; jade?: boolean }) => (
|
||
<Card
|
||
overline={slot}
|
||
jade={jade}
|
||
title={
|
||
<a
|
||
href="#"
|
||
onClick={(e) => {
|
||
e.preventDefault()
|
||
onOpenIssue({ id: issue.id, title: issue.title, labels: issue.labels })
|
||
}}
|
||
style={{ color: 'inherit', border: 'none' }}
|
||
>
|
||
{issue.title}
|
||
</a>
|
||
}
|
||
actions={<IconButton icon="ellipsis" label="More" size="sm" />}
|
||
footer={
|
||
jade ? (
|
||
<>
|
||
<Button size="sm">Start</Button>
|
||
<Button size="sm" variant="ghost">
|
||
Defer
|
||
</Button>
|
||
<span style={{ marginLeft: 'auto', font: 'var(--text-caption)', color: 'var(--ink-3)' }}>
|
||
scheduler pick · critical path
|
||
</span>
|
||
</>
|
||
) : null
|
||
}
|
||
>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, flexWrap: 'wrap' }}>
|
||
<span style={{ font: 'var(--text-data)', color: 'var(--ink-3)' }}>#{issue.id}</span>
|
||
{issue.labels.map((l) => (
|
||
<Tag key={l} label={l} />
|
||
))}
|
||
{issue.steeping ? (
|
||
<Badge tone="warn" dot>
|
||
steeping {issue.steeping}
|
||
</Badge>
|
||
) : null}
|
||
</div>
|
||
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: 0 }}>{issue.rationale}</p>
|
||
</Card>
|
||
)
|
||
|
||
return (
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||
<header
|
||
style={{
|
||
borderBottom: 'var(--rule-double)',
|
||
paddingBottom: 14,
|
||
display: 'flex',
|
||
alignItems: 'baseline',
|
||
justifyContent: 'space-between',
|
||
gap: 12,
|
||
}}
|
||
>
|
||
<div>
|
||
<h1 style={{ font: 'var(--text-display)', color: 'var(--ink-1)', margin: 0 }}>Morning service</h1>
|
||
<p style={{ font: 'var(--text-data)', color: 'var(--ink-3)', margin: '6px 0 0', whiteSpace: 'nowrap' }}>
|
||
{TODAY} · reconcile 3.2s
|
||
</p>
|
||
</div>
|
||
<Badge tone="ok" dot>
|
||
ahead of forecast
|
||
</Badge>
|
||
</header>
|
||
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1.4fr 1fr 1fr', gap: 14, alignItems: 'start' }}>
|
||
<FocusRow slot="Now" issue={FOCUS.now} jade />
|
||
<FocusRow slot="Next" issue={FOCUS.next} />
|
||
<FocusRow slot="Later" issue={FOCUS.later} />
|
||
</div>
|
||
|
||
<Card
|
||
overline="Milestone · Beta"
|
||
title={<>80% this lands <span style={{ whiteSpace: 'nowrap' }}>Mar 3–12</span></>}
|
||
actions={<IconButton icon="chart-line" label="Open runway" size="sm" />}
|
||
>
|
||
<BurnUpCone />
|
||
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: '10px 0 0' }}>
|
||
The cone has narrowed since Friday. I’m quietly pleased.
|
||
</p>
|
||
</Card>
|
||
</div>
|
||
)
|
||
}
|