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>
This commit is contained in:
Christian LeDoux
2026-07-07 20:42:46 -04:00
commit 7a5cacc54c
268 changed files with 15766 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
// Calibration report — estimate-vs-actual evidence behind the cones
function CalibrationScreen({ onBack }) {
const DS = window.CommiTeaDesignSystem_20e63b;
const { Card, Badge, Icon } = DS;
const c = window.CT_DATA.calibration;
// scatter chart geometry
const W = 420, H = 300, pad = { l: 36, r: 16, t: 14, b: 30 };
const maxD = 9;
const X = (d) => pad.l + (d / maxD) * (W - pad.l - pad.r);
const Y = (d) => H - pad.b - (d / maxD) * (H - pad.t - pad.b);
const BiasBar = ({ bias }) => {
if (bias == null) return <span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)' }}>n too small</span>;
return (
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flex: 1 }}>
<div style={{ position: 'relative', flex: 1, height: 10, background: 'var(--paper-2)', borderRadius: 3 }}>
<div style={{ position: 'absolute', left: '30%', top: -2, bottom: -2, width: 1.5, background: 'var(--line-2)' }}></div>
<div style={{
position: 'absolute', left: '30%', top: 1.5, height: 7, width: `${Math.min(bias * 1.6, 66)}%`,
background: bias > 15 ? 'var(--warn)' : 'var(--ok)', borderRadius: '0 3px 3px 0', opacity: 0.75,
}}></div>
</div>
<span style={{ font: '500 11.5px var(--font-mono)', color: bias > 15 ? 'var(--warn)' : 'var(--ok)', width: 42, textAlign: 'right' }}>+{bias}%</span>
</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: 'baseline', justifyContent: 'space-between', gap: 12 }}>
<div>
<h1 style={{ font: 'var(--text-display)', color: 'var(--ink-1)', margin: 0 }}>Calibration</h1>
<p style={{ font: 'var(--text-data)', color: 'var(--ink-3)', margin: '6px 0 0', whiteSpace: 'nowrap' }}>{c.n} closed issues with estimates · evidence, not opinion</p>
</div>
<Badge tone="ok" dot>curve active · n ≥ 20</Badge>
</header>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14, alignItems: 'start' }}>
{/* scatter */}
<Card overline="Estimate vs actual" title="The shape of hope" jade>
<svg width="100%" viewBox={`0 0 ${W} ${H}`} style={{ display: 'block' }}>
{[1, 3, 5, 8].map((d) => (
<React.Fragment key={d}>
<line x1={X(d)} x2={X(d)} y1={pad.t} y2={H - pad.b} stroke="var(--line-1)" strokeWidth="1" />
<text x={X(d)} y={H - 12} textAnchor="middle" style={{ font: '400 10px var(--font-mono)', fill: 'var(--ink-3)' }}>{d}d</text>
<line x1={pad.l} x2={W - pad.r} y1={Y(d)} y2={Y(d)} stroke="var(--line-1)" strokeWidth="1" />
<text x={pad.l - 6} y={Y(d) + 3} textAnchor="end" style={{ font: '400 10px var(--font-mono)', fill: 'var(--ink-3)' }}>{d}d</text>
</React.Fragment>
))}
{/* perfect line */}
<line x1={X(0)} y1={Y(0)} x2={X(maxD)} y2={Y(maxD)} stroke="var(--line-2)" strokeWidth="1.2" strokeDasharray="4 4" />
<text x={X(7.6)} y={Y(7.6) + 14} style={{ font: '400 10px var(--font-mono)', fill: 'var(--ink-3)' }}>honest</text>
{/* fit */}
<line x1={X(0)} y1={Y(0)} x2={X(maxD / c.fit)} y2={Y(maxD)} stroke="var(--cone-line)" strokeWidth="1.6" />
<text x={X(4.1)} y={Y(4.1 * c.fit) - 8} style={{ font: '500 10px var(--font-mono)', fill: 'var(--cone-line)' }}>you · ×{c.fit}</text>
{/* points */}
{c.scatter.map(([e, a], i) => (
<circle key={i} cx={X(e) + ((i % 5) - 2) * 3} cy={Y(a)} r="3" fill="var(--accent)" opacity="0.55" />
))}
</svg>
<p style={{ font: 'var(--text-caption)', color: 'var(--ink-3)', margin: '8px 0 0' }}>estimated (x) vs actual days (y) · actuals inferred from git events, never tracked</p>
</Card>
<div style={{ display: 'flex', flexDirection: 'column', gap: 14 }}>
{/* per-label bias */}
<Card overline="Bias by estimate label" flush>
<div>
{c.labels.map((r, i) => (
<div key={r.label} style={{
display: 'flex', alignItems: 'center', gap: 14, padding: '10px 20px',
borderTop: i === 0 ? 'none' : '1px solid var(--line-1)',
}}>
<span style={{ font: '500 11.5px var(--font-mono)', color: 'var(--ink-1)', width: 52 }}>{r.label}</span>
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)', width: 66 }}>n={r.n} · {r.median}</span>
<BiasBar bias={r.bias} />
</div>
))}
</div>
</Card>
{/* per-person */}
<Card overline="By person" flush>
<div>
{c.people.map((p, i) => (
<div key={p.who} style={{
display: 'flex', alignItems: 'center', gap: 12, padding: '11px 20px',
borderTop: i === 0 ? 'none' : '1px solid var(--line-1)',
}}>
<span style={{
width: 24, height: 24, borderRadius: '50%', background: 'var(--spruce-2)', color: 'var(--accent-text)',
font: '600 9px/24px var(--font-sans)', textAlign: 'center', flexShrink: 0,
}}>{p.who.split(' ').map((w) => w[0]).join('')}</span>
<div style={{ flex: 1, minWidth: 0 }}>
<span style={{ font: 'var(--text-body-strong)', color: 'var(--ink-1)' }}>{p.who}</span>
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)' }}> · n={p.n} · {p.note}</span>
</div>
<span style={{ font: '500 12px var(--font-mono)', color: p.bias > 15 ? 'var(--warn)' : 'var(--ok)' }}>+{p.bias}%</span>
</div>
))}
</div>
</Card>
{/* effect on forecasts */}
<Card overline="What this does to your forecasts">
<div style={{ display: 'flex', alignItems: 'center', gap: 10, flexWrap: 'wrap' }}>
<span style={{ font: '400 12.5px var(--font-mono)', color: 'var(--ink-2)', whiteSpace: 'nowrap' }}>{c.effect.raw}</span>
<Icon name="arrow-right" size={14} style={{ color: 'var(--ink-3)' }} />
<span style={{ font: '500 12.5px var(--font-mono)', color: 'var(--ink-1)', whiteSpace: 'nowrap' }}>{c.effect.banded}</span>
</div>
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: '10px 0 0' }}>
You are not bad at estimating; you are optimistic in a very stable way. Stable, I can work with.
</p>
</Card>
</div>
</div>
</div>
);
}
Object.assign(window, { CalibrationScreen });