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

173 lines
10 KiB
Plaintext

// Issue detail — human intent (gitea) on the left, machine-derived (pm-state) on the right
function IssueScreen({ issue, onBack, onOpenIssue }) {
const DS = window.CommiTeaDesignSystem_20e63b;
const { Card, Tag, Badge, Button, Icon } = DS;
const det = window.CT_DATA.issueDetail[issue.id] || {
state: 'triage',
assignee: 'Stephen',
milestone: 'Beta',
body: '',
comments: [],
lifecycle: [
{ stage: 'Diagnosis', event: 'issue opened', when: 'Feb 4 · 10:20', icon: 'circle-dot', done: true },
{ stage: 'Triage', event: 'labeled · milestoned Beta', when: 'Feb 5 · 09:12', icon: 'tag', done: true },
{ stage: 'Work start', event: 'first branch or commit ref', when: 'pending', icon: 'git-commit-horizontal', done: false },
{ stage: 'Deploy', event: 'PR merged', when: 'pending', icon: 'git-merge', done: false },
{ stage: 'Complete', event: 'issue closed', when: 'pending', icon: 'circle-check', done: false },
],
forecast: { p80: 'starts wk of Feb 16', note: 'queue position from scheduler' },
blocks: [],
blockedBy: [],
note: null,
};
const stateBadge = {
steeping: { tone: 'warn', label: `steeping${issue.days || issue.steeping ? ' ' + (issue.days || issue.steeping) : ''}` },
triage: { tone: 'neutral', label: 'triage' },
review: { tone: 'info', label: 'in review' },
done: { tone: 'ok', label: 'done' },
}[det.state] || { tone: 'neutral', label: det.state };
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
{/* breadcrumb + header */}
<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} /> Back
</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 13px var(--font-mono)', color: 'var(--ink-3)', margin: '0 0 6px' }}>#{issue.id} · stephen/commitea</p>
<h1 style={{ font: 'var(--text-title)', color: 'var(--ink-1)', margin: 0 }}>{issue.title}</h1>
<div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 10, flexWrap: 'wrap' }}>
<Badge tone={stateBadge.tone} dot>{stateBadge.label}</Badge>
{(issue.labels || []).map((l) => <Tag key={l} label={l} />)}
<span style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)', display: 'inline-flex', alignItems: 'center', gap: 5 }}>
<Icon name="milestone" size={12} />{det.milestone}
</span>
<span style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)', display: 'inline-flex', alignItems: 'center', gap: 5 }}>
<Icon name="user" size={12} />{det.assignee}
</span>
</div>
</div>
<Button variant="secondary" icon="arrow-up-right">Open in Gitea</Button>
</header>
</div>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 300px', gap: 16, alignItems: 'start' }}>
{/* left: human intent */}
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
<Card overline="Description">
{det.body ? (
<p style={{ font: 'var(--text-body)', color: 'var(--ink-1)', margin: 0, lineHeight: 1.6 }}>{det.body}</p>
) : (
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-3)', margin: 0 }}>
No description was written. I have opinions about that, but I'll keep them warm.
</p>
)}
</Card>
<Card overline={`Comments · ${det.comments.length}`} flush>
<div>
{det.comments.map((c, i) => (
<div key={i} style={{ display: 'flex', gap: 12, padding: '14px 20px', borderBottom: '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,
}}>{c.who.split(' ').map((w) => w[0]).join('')}</span>
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 8 }}>
<span style={{ font: 'var(--text-body-strong)', color: 'var(--ink-1)', whiteSpace: 'nowrap' }}>{c.who}</span>
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)', whiteSpace: 'nowrap' }}>{c.when}</span>
</div>
<p style={{ font: 'var(--text-body)', color: 'var(--ink-1)', margin: '4px 0 0' }}>{c.text}</p>
</div>
</div>
))}
<div style={{ display: 'flex', gap: 8, padding: '14px 20px', alignItems: 'flex-end' }}>
<textarea placeholder="Comment — this writes to gitea, as you" rows={2} style={{
flex: 1, resize: 'none', font: 'var(--text-body)', color: 'var(--ink-1)', lineHeight: 1.5,
background: 'var(--paper-0)', border: '1px solid var(--line-2)', borderRadius: 'var(--radius-2)',
padding: '8px 11px', outline: 'none',
}}></textarea>
<Button size="sm" variant="secondary">Comment</Button>
</div>
</div>
</Card>
{det.note ? (
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: 0, padding: '0 2px' }}>{det.note}</p>
) : null}
</div>
{/* right: machine-derived sidecar */}
<Card overline="Machine-derived" flush>
<div style={{ display: 'flex', flexDirection: 'column' }}>
{/* lifecycle */}
<div style={{ padding: '14px 20px', display: 'flex', flexDirection: 'column', gap: 0 }}>
{det.lifecycle.map((s, i) => (
<div key={s.stage} style={{ display: 'flex', gap: 10 }}>
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
<span style={{ display: 'inline-flex', color: s.done ? 'var(--ok)' : 'var(--ink-3)', padding: '2px 0' }}>
<Icon name={s.icon} size={14} />
</span>
{i < det.lifecycle.length - 1 ? <span style={{ width: 1, flex: 1, minHeight: 14, background: s.done ? 'var(--spruce-3)' : 'var(--line-1)' }}></span> : null}
</div>
<div style={{ paddingBottom: i < det.lifecycle.length - 1 ? 12 : 0 }}>
<div style={{ font: `500 12px var(--font-sans)`, color: s.done ? 'var(--ink-1)' : 'var(--ink-3)' }}>{s.stage}</div>
<div style={{ font: '400 10.5px var(--font-mono)', color: 'var(--ink-3)', marginTop: 2, whiteSpace: 'nowrap' }}>{s.event}</div>
<div style={{ font: '400 10.5px var(--font-mono)', color: s.done ? 'var(--ink-2)' : 'var(--ink-3)', whiteSpace: 'nowrap' }}>{s.when}</div>
</div>
</div>
))}
</div>
{/* forecast */}
<div style={{ padding: '12px 20px', borderTop: '1px solid var(--line-1)' }}>
<div style={{ font: 'var(--text-overline)', letterSpacing: 'var(--letter-spacing-wide)', textTransform: 'uppercase', color: 'var(--ink-3)', marginBottom: 6 }}>Forecast</div>
<div style={{ font: '500 13px var(--font-mono)', color: 'var(--ink-1)' }}>80% {det.forecast.p80}</div>
<div style={{ font: '400 10.5px var(--font-mono)', color: 'var(--ink-3)', marginTop: 3 }}>{det.forecast.note}</div>
</div>
{/* dependencies */}
<div style={{ padding: '12px 20px', borderTop: '1px solid var(--line-1)' }}>
<div style={{ font: 'var(--text-overline)', letterSpacing: 'var(--letter-spacing-wide)', textTransform: 'uppercase', color: 'var(--ink-3)', marginBottom: 6 }}>Dependencies</div>
{det.blocks.length === 0 && det.blockedBy.length === 0 ? (
<div style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)' }}>none</div>
) : (
<div style={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{det.blocks.length ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 6, flexWrap: 'wrap' }}>
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)' }}>blocks</span>
{det.blocks.map((b) => (
<button key={b} type="button"
onClick={() => onOpenIssue({ id: b, title: b === 91 ? 'Webhook listener: reconcile on reconnect' : 'Monte Carlo engine: percentile bands', labels: b === 91 ? ['est/3d', 'p/2'] : ['est/5d', 'p/1'] })}
style={{ font: '500 11px var(--font-mono)', color: 'var(--accent-text)', background: 'var(--spruce-1)', border: '1px solid var(--spruce-2)', borderRadius: 'var(--radius-1)', padding: '2px 7px', cursor: 'pointer' }}>
#{b}
</button>
))}
</div>
) : null}
{det.blockedBy.length ? (
<div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)' }}>blocked by</span>
{det.blockedBy.map((b) => <span key={b} style={{ font: '500 11px var(--font-mono)', color: 'var(--ink-2)' }}>#{b}</span>)}
</div>
) : null}
</div>
)}
</div>
{/* provenance note */}
<div style={{ padding: '10px 20px 14px', borderTop: '1px solid var(--line-1)' }}>
<p style={{ font: 'var(--text-caption)', color: 'var(--ink-3)', margin: 0 }}>
Lives in pm-state. Your repo never sees any of it.
</p>
</div>
</div>
</Card>
</div>
</div>
);
}
Object.assign(window, { IssueScreen });