Files
commitea/docs/design/ui_kits/app/MilestoneScreen.js.txt
Christian LeDoux 7a5cacc54c Scaffold CommiTea: yarn workspaces, Electron shell, core label schema, design system
- 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>
2026-07-07 20:42:46 -04:00

99 lines
5.4 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 312</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 312" />
<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 312</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 });