- 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>
74 lines
3.8 KiB
Plaintext
74 lines
3.8 KiB
Plaintext
// Runway — capacity vs milestone dates; ranges, never points
|
|
function RunwayScreen({ onOpenCalibration, onOpenMilestone }) {
|
|
const DS = window.CommiTeaDesignSystem_20e63b;
|
|
const { Card, Badge, Tag, Icon } = DS;
|
|
const d = window.CT_DATA;
|
|
|
|
return (
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
|
<header style={{ borderBottom: 'var(--rule-double)', paddingBottom: 14 }}>
|
|
<h1 style={{ font: 'var(--text-display)', color: 'var(--ink-1)', margin: 0 }}>Runway</h1>
|
|
<p style={{ font: 'var(--text-data)', color: 'var(--ink-3)', margin: '6px 0 0' }}>capacity vs milestone dates · calibrated on 27 closed issues</p>
|
|
</header>
|
|
|
|
<Card overline="Milestones" flush>
|
|
<div>
|
|
{d.runway.map((m, i) => (
|
|
<div key={m.name} onClick={onOpenMilestone} style={{
|
|
display: 'grid', gridTemplateColumns: '160px 1fr 150px 90px', gap: 16, alignItems: 'center', cursor: 'pointer',
|
|
padding: '14px 20px', borderTop: i === 0 ? 'none' : '1px solid var(--line-1)',
|
|
}}
|
|
onMouseEnter={(e) => { e.currentTarget.style.background = 'var(--paper-2)'; }}
|
|
onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}>
|
|
<div>
|
|
<div style={{ font: 'var(--text-body-strong)', color: 'var(--ink-1)', display: 'flex', alignItems: 'center', gap: 7 }}>
|
|
<Icon name="milestone" size={14} style={{ color: 'var(--ink-3)' }} />{m.name}
|
|
</div>
|
|
<div style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)', marginTop: 3 }}>
|
|
due {m.due}{m.hard ? ' ' : ''}
|
|
</div>
|
|
{m.hard ? <Tag label="deadline/hard" style={{ marginTop: 5 }} /> : null}
|
|
</div>
|
|
<window.RunwayBar m={m} />
|
|
<span style={{ font: '400 12px var(--font-mono)', color: 'var(--ink-2)' }}>80% {m.p80}</span>
|
|
<Badge tone={m.tone} dot>{m.note}</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
|
|
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, alignItems: 'start' }}>
|
|
<Card overline="Capacity" flush>
|
|
<div>
|
|
{d.capacity.map((p, i) => (
|
|
<div key={p.who} style={{
|
|
display: 'flex', alignItems: 'center', gap: 12, padding: '12px 20px',
|
|
borderTop: i === 0 ? 'none' : '1px solid var(--line-1)',
|
|
}}>
|
|
<span style={{
|
|
width: 26, height: 26, borderRadius: '50%', background: 'var(--spruce-2)', color: 'var(--accent-text)',
|
|
font: '600 10px/26px var(--font-sans)', textAlign: 'center', flexShrink: 0,
|
|
}}>{p.who.split(' ').map(w => w[0]).join('')}</span>
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
<div style={{ font: 'var(--text-body-strong)' }}>{p.who}</div>
|
|
<div style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)', marginTop: 2 }}>{p.slices}</div>
|
|
</div>
|
|
<span style={{ font: '400 12px var(--font-mono)', color: 'var(--ink-2)' }}>{p.hours}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Card>
|
|
<Card overline="Calibration" actions={<DS.IconButton icon="arrow-up-right" label="Full report" size="sm" onClick={onOpenCalibration} />}>
|
|
<p style={{ font: 'var(--text-body)', margin: '0 0 8px' }}>
|
|
Your estimates run <strong>18% optimistic</strong> on <code>est/3d</code> and above. Smaller tickets are honest.
|
|
</p>
|
|
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: 0 }}>
|
|
I widen the cone accordingly. No judgement — it's the most common shape of hope.
|
|
</p>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
Object.assign(window, { RunwayScreen });
|