Files
commitea/docs/design/ui_kits/app/StandupScreen.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

113 lines
5.9 KiB
Plaintext

// Morning standup ritual — a typeset letter from Reginald: drift, plan, nag
const standupCSS = `
@keyframes ct-standup-settle {
from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: none; }
}
.ct-standup-section { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
.ct-standup-section { animation: ct-standup-settle 320ms var(--ease-out) both; }
}
`;
(function inject() {
if (typeof document !== 'undefined' && !document.getElementById('ct-standup-css')) {
const s = document.createElement('style'); s.id = 'ct-standup-css'; s.textContent = standupCSS;
document.head.appendChild(s);
}
})();
function StandupScreen({ onBegin, onOpenIssue }) {
const DS = window.CommiTeaDesignSystem_20e63b;
const { Button, Tag, Badge, Icon } = DS;
const s = window.CT_DATA.standup;
const Section = ({ overline, children, order }) => (
<section className="ct-standup-section" style={{ animationDelay: `${order * 90}ms`, display: 'flex', flexDirection: 'column', gap: 12 }}>
<p style={{
font: 'var(--text-overline)', letterSpacing: 'var(--letter-spacing-wide)', textTransform: 'uppercase',
color: 'var(--ink-3)', margin: 0, borderTop: '1px solid var(--line-1)', paddingTop: 14,
}}>{overline}</p>
{children}
</section>
);
const toneColor = { ok: 'var(--ok)', warn: 'var(--warn)', danger: 'var(--danger)' };
return (
<div style={{ maxWidth: 660, margin: '0 auto' }}>
<article style={{
background: 'var(--surface-card)', border: '1px solid var(--line-1)',
borderRadius: 'var(--radius-3)', boxShadow: 'var(--shadow-jade-line), var(--shadow-1)',
padding: '36px 44px 32px', display: 'flex', flexDirection: 'column', gap: 20,
}}>
{/* letterhead */}
<header className="ct-standup-section" style={{ borderBottom: 'var(--rule-double)', paddingBottom: 16 }}>
<p style={{ font: 'var(--text-data)', color: 'var(--ink-3)', margin: '0 0 8px' }}>{s.date} · prepared 07:00</p>
<h1 style={{ font: 'var(--text-display)', color: 'var(--ink-1)', margin: 0 }}>Morning standup</h1>
</header>
<Section overline="Overnight drift" order={1}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
{s.drift.map((d) => (
<div key={d.text} style={{ display: 'flex', alignItems: 'baseline', gap: 10 }}>
<span style={{ width: 7, height: 7, borderRadius: '50%', background: toneColor[d.tone], flexShrink: 0, position: 'relative', top: -1 }}></span>
<span style={{ font: 'var(--text-body)', color: 'var(--ink-1)', flex: 1 }}>{d.text}</span>
<span style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)', whiteSpace: 'nowrap' }}>{d.delta}</span>
</div>
))}
</div>
</Section>
<Section overline="Today's plan" order={2}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
{s.plan.map((p) => (
<div key={p.who} style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
<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={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<span style={{ font: 'var(--text-body-strong)', color: 'var(--ink-1)', whiteSpace: 'nowrap' }}>{p.who}</span>
<span style={{ font: '400 12px var(--font-mono)', color: 'var(--ink-2)' }}>{p.pick}</span>
<span style={{ font: 'var(--text-small)', color: 'var(--ink-2)' }}>{p.title}</span>
</div>
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: '4px 0 0' }}>{p.why}</p>
</div>
</div>
))}
</div>
</Section>
<Section overline="Stale blockers" order={3}>
<div
onClick={() => onOpenIssue({ id: s.nag.id, title: 'Fix lifecycle inference on merge events', labels: ['est/2d', 'p/1'], days: s.nag.days })}
style={{
display: 'flex', gap: 10, alignItems: 'flex-start', cursor: 'pointer',
background: 'var(--warn-tint)', borderRadius: 'var(--radius-2)', padding: '12px 14px',
}}
>
<span style={{ color: 'var(--warn)', display: 'inline-flex', marginTop: 2 }}><Icon name="clock" size={15} /></span>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
<span style={{ font: '500 12.5px var(--font-mono)', color: 'var(--ink-1)' }}>#{s.nag.id}</span>
<Badge tone="warn" dot>steeping {s.nag.days}</Badge>
<span style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)' }}>blocks {s.nag.blocks.join(', ')}</span>
</div>
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: '5px 0 0' }}>{s.nag.text}</p>
</div>
</div>
</Section>
{/* sign-off */}
<footer className="ct-standup-section" style={{ animationDelay: '360ms', borderTop: '1px solid var(--line-1)', paddingTop: 16, display: 'flex', alignItems: 'center', gap: 10 }}>
<p style={{ font: 'var(--text-agent-lg)', color: 'var(--ink-1)', margin: 0, flex: 1 }}>The kettle's on. — R.</p>
<Button variant="ghost" onClick={onBegin}>Ask about the drift</Button>
<Button iconRight="arrow-right" onClick={onBegin}>Begin the day</Button>
</footer>
</article>
</div>
);
}
Object.assign(window, { StandupScreen });