- 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>
104 lines
6.0 KiB
Plaintext
104 lines
6.0 KiB
Plaintext
// Directive log — append-only ledger + the consequence diff (propose-approve)
|
||
function DirectivesScreen() {
|
||
const DS = window.CommiTeaDesignSystem_20e63b;
|
||
const { Card, Button, Badge, Icon } = DS;
|
||
const d = window.CT_DATA.directives;
|
||
const [pending, setPending] = React.useState(d.pending);
|
||
const [entries, setEntries] = React.useState(d.entries);
|
||
|
||
const resolve = (status) => {
|
||
setEntries((e) => [{
|
||
seq: pending.seq, who: pending.who, when: pending.when, what: pending.what, why: 'pilot demo on the 14th',
|
||
status, consequence: status === 'applied' ? '#78 +5d · Beta 80% Mar 5–14' : 'withdrawn before apply',
|
||
}, ...e]);
|
||
setPending(null);
|
||
};
|
||
|
||
const toneColor = { ok: 'var(--ok)', warn: 'var(--warn)', info: 'var(--info)', danger: 'var(--danger)' };
|
||
const statusBadge = {
|
||
applied: { tone: 'ok', label: 'applied' },
|
||
withdrawn: { tone: 'neutral', label: 'withdrawn' },
|
||
superseded: { tone: 'info', label: 'superseded' },
|
||
};
|
||
|
||
return (
|
||
<div style={{ maxWidth: 760, margin: '0 auto', 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 }}>Directives</h1>
|
||
<p style={{ font: 'var(--text-data)', color: 'var(--ink-3)', margin: '6px 0 0' }}>append-only · JSONL in pm-state · who, when, what, why</p>
|
||
</header>
|
||
|
||
{pending ? (
|
||
<Card overline={`Awaiting your word · directive #00${pending.seq}`} title="Consequence diff" jade
|
||
footer={<>
|
||
<Button onClick={() => resolve('applied')}>Make it so</Button>
|
||
<Button variant="secondary" onClick={() => {}}>Amend</Button>
|
||
<Button variant="ghost" onClick={() => resolve('withdrawn')}>Withdraw</Button>
|
||
</>}>
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||
<p style={{ font: 'var(--text-body)', margin: 0, color: 'var(--ink-2)' }}>
|
||
<span style={{ font: 'var(--text-body-strong)', color: 'var(--ink-1)' }}>{pending.who}</span>
|
||
<span style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-3)' }}> · {pending.when}</span>
|
||
<br />“{pending.what}”
|
||
</p>
|
||
<div style={{ borderTop: '1px solid var(--line-1)' }}>
|
||
{pending.diff.map((r) => (
|
||
<div key={r.change} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '9px 0', borderBottom: '1px solid var(--line-1)' }}>
|
||
<span style={{ width: 7, height: 7, borderRadius: '50%', background: toneColor[r.tone], flexShrink: 0 }}></span>
|
||
<span style={{ font: 'var(--text-small)', color: 'var(--ink-1)', flex: 1 }}>{r.change}</span>
|
||
<span style={{ font: '400 12px var(--font-mono)', color: 'var(--ink-3)', whiteSpace: 'nowrap' }}>
|
||
{r.from} <span style={{ color: 'var(--ink-2)' }}>→</span> <span style={{ color: r.tone === 'warn' ? 'var(--warn)' : 'var(--ink-1)' }}>{r.to}</span>
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: 0 }}>
|
||
Cheap, as consequences go. Shall I make it so?
|
||
</p>
|
||
</div>
|
||
</Card>
|
||
) : (
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, font: 'var(--text-small)', color: 'var(--ink-3)', padding: '2px 2px' }}>
|
||
<Icon name="circle-check" size={14} style={{ color: 'var(--ok)' }} />
|
||
Nothing awaits your word. Directives are given in chat; consequences appear here first.
|
||
</div>
|
||
)}
|
||
|
||
<Card overline="The ledger" flush>
|
||
<div>
|
||
{entries.map((e, i) => {
|
||
const sb = statusBadge[e.status];
|
||
return (
|
||
<div key={e.seq} style={{ display: 'flex', gap: 14, padding: '14px 20px', borderTop: i === 0 ? 'none' : '1px solid var(--line-1)' }}>
|
||
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6, flexShrink: 0 }}>
|
||
<span style={{ font: '500 11px var(--font-mono)', color: 'var(--ink-3)' }}>#00{e.seq}</span>
|
||
<span style={{ width: 1, flex: 1, background: 'var(--line-1)' }}></span>
|
||
</div>
|
||
<div style={{ flex: 1, minWidth: 0, display: 'flex', flexDirection: 'column', gap: 5 }}>
|
||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
|
||
<span style={{
|
||
width: 20, height: 20, borderRadius: '50%', background: 'var(--spruce-2)', color: 'var(--accent-text)',
|
||
font: '600 8.5px/20px var(--font-sans)', textAlign: 'center', flexShrink: 0,
|
||
}}>{e.who.split(' ').map((w) => w[0]).join('')}</span>
|
||
<span style={{ font: 'var(--text-body-strong)', color: 'var(--ink-1)', whiteSpace: 'nowrap' }}>{e.who}</span>
|
||
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)', whiteSpace: 'nowrap' }}>{e.when}</span>
|
||
{e.why ? <span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)', whiteSpace: 'nowrap' }}>· why: {e.why}</span> : null}
|
||
<span style={{ marginLeft: 'auto' }}><Badge tone={sb.tone}>{sb.label}</Badge></span>
|
||
</div>
|
||
<p style={{ font: 'var(--text-body)', color: 'var(--ink-1)', margin: 0 }}>“{e.what}”</p>
|
||
<p style={{ font: '400 11.5px var(--font-mono)', color: 'var(--ink-2)', margin: 0 }}>{e.consequence}</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</Card>
|
||
|
||
<p style={{ font: 'var(--text-caption)', color: 'var(--ink-3)', margin: 0 }}>
|
||
Entries are never edited. Corrections are new entries — the ledger remembers everything, politely.
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|
||
Object.assign(window, { DirectivesScreen });
|