- apps/desktop: electron-vite + React + Tailwind mapped onto design tokens (preflight off; tokens/base.css owns the reset); boots to a Reginald placeholder proving fonts/tokens/core wiring - packages/core: pure TS; gitea label schema (est/*, p/*, deadline/hard) with pessimistic conflict resolution + 15 unit tests - docs/design: full design handoff (tokens, 16 component contracts, interactive 14-screen prototype, Reginald voice rules) - docs/PLAN.md: product plan (purity rule, pm-state repo, deterministic scheduler + Monte Carlo, directive log) - Deliberate deviation from novelpad stack: no ElectricSQL/PGlite — local store is a rebuildable cache over gitea REST/webhooks (better-sqlite3 in main process, arriving in P1) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
99 lines
5.4 KiB
Plaintext
99 lines
5.4 KiB
Plaintext
// Milestone detail — scope, cone, issues; forecasts stay ranges
|
||
function MilestoneScreen({ onBack, onOpenIssue }) {
|
||
const DS = window.CommiTeaDesignSystem_20e63b;
|
||
const { Card, Tag, Badge, Button, Icon } = DS;
|
||
const cols = window.CT_DATA.columns;
|
||
const byState = (ids) => cols.flatMap((c) => c.issues.map((i) => ({ ...i, col: c.label }))).filter((i) => ids.includes(i.id));
|
||
|
||
const groups = [
|
||
{ 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 },
|
||
];
|
||
|
||
const Stat = ({ label, value, tone }) => (
|
||
<div style={{ flex: 1, padding: '12px 18px', borderRight: '1px solid var(--line-1)' }}>
|
||
<div style={{ font: 'var(--text-overline)', letterSpacing: 'var(--letter-spacing-wide)', textTransform: 'uppercase', color: 'var(--ink-3)', marginBottom: 5 }}>{label}</div>
|
||
<div style={{ font: `500 14px var(--font-mono)`, color: tone || 'var(--ink-1)', whiteSpace: 'nowrap' }}>{value}</div>
|
||
</div>
|
||
);
|
||
|
||
return (
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||
<div>
|
||
<button type="button" onClick={onBack} style={{
|
||
display: 'inline-flex', alignItems: 'center', gap: 6, background: 'none', border: 'none',
|
||
font: '500 12.5px var(--font-sans)', color: 'var(--ink-2)', cursor: 'pointer', padding: '2px 0', marginBottom: 10,
|
||
}}>
|
||
<Icon name="arrow-left" size={14} /> Runway
|
||
</button>
|
||
<header style={{ borderBottom: 'var(--rule-double)', paddingBottom: 14, display: 'flex', alignItems: 'flex-start', gap: 16 }}>
|
||
<div style={{ flex: 1, minWidth: 0 }}>
|
||
<p style={{ font: '400 12px var(--font-mono)', color: 'var(--ink-3)', margin: '0 0 6px', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
||
<Icon name="milestone" size={13} /> milestone · due Mar 15 · soft — scope may flex
|
||
</p>
|
||
<h1 style={{ font: 'var(--text-display)', color: 'var(--ink-1)', margin: 0 }}>Beta</h1>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10 }}>
|
||
<Badge tone="ok" dot>ahead of forecast</Badge>
|
||
<span style={{ font: '400 12px var(--font-mono)', color: 'var(--ink-2)', whiteSpace: 'nowrap' }}>80% Mar 3–12</span>
|
||
</div>
|
||
</div>
|
||
<Button variant="secondary" icon="arrow-up-right">Open in Gitea</Button>
|
||
</header>
|
||
</div>
|
||
|
||
{/* stats strip */}
|
||
<Card flush>
|
||
<div style={{ display: 'flex' }}>
|
||
<Stat label="Scope" value="42 issues · est 61d" />
|
||
<Stat label="Done" value="24 · 57%" />
|
||
<Stat label="Forecast" value="80% Mar 3–12" />
|
||
<div style={{ flex: 1, padding: '12px 18px' }}>
|
||
<div style={{ font: 'var(--text-overline)', letterSpacing: 'var(--letter-spacing-wide)', textTransform: 'uppercase', color: 'var(--ink-3)', marginBottom: 5 }}>Drift · 7d</div>
|
||
<div style={{ font: '500 14px var(--font-mono)', color: 'var(--ok)', whiteSpace: 'nowrap' }}>−2d · cone narrowed</div>
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
|
||
<div style={{ display: 'grid', gridTemplateColumns: '1.2fr 1fr', gap: 14, alignItems: 'start' }}>
|
||
<Card overline="Burn-up" title={<>80% this lands <span style={{ whiteSpace: 'nowrap' }}>Mar 3–12</span></>} jade>
|
||
<window.BurnUpCone />
|
||
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: '10px 0 0' }}>
|
||
Comfortably ahead. Beta needs #87 more than it needs my commentary.
|
||
</p>
|
||
</Card>
|
||
|
||
<Card overline="Issues" flush>
|
||
<div>
|
||
{groups.map((g) => (
|
||
<div key={g.label}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 7, padding: '10px 20px 6px' }}>
|
||
<span style={{ font: 'var(--text-overline)', letterSpacing: 'var(--letter-spacing-wide)', textTransform: 'uppercase', color: 'var(--ink-3)' }}>{g.label}</span>
|
||
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)' }}>{g.issues.length}</span>
|
||
</div>
|
||
{g.issues.map((i) => (
|
||
<div key={i.id}
|
||
onClick={() => onOpenIssue({ id: i.id, title: i.title, labels: i.labels, days: i.days })}
|
||
style={{
|
||
display: 'flex', alignItems: 'center', gap: 8, padding: '7px 20px', cursor: 'pointer',
|
||
opacity: g.muted ? 0.6 : 1,
|
||
}}
|
||
onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--paper-2)'; }}
|
||
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
|
||
>
|
||
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)', width: 34, flexShrink: 0 }}>#{i.id}</span>
|
||
<span style={{ font: 'var(--text-small)', color: 'var(--ink-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', flex: 1 }}>{i.title}</span>
|
||
{(i.labels || []).filter((l) => l.startsWith('est/')).map((l) => <Tag key={l} label={l} />)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
))}
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
Object.assign(window, { MilestoneScreen });
|