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:
103
docs/design/ui_kits/app/GanttView.js.txt
Normal file
103
docs/design/ui_kits/app/GanttView.js.txt
Normal file
@@ -0,0 +1,103 @@
|
||||
// Gantt drill-in — scheduler-derived bars, critical chain, 80% forecast tails
|
||||
function GanttView({ onOpenIssue }) {
|
||||
const DS = window.CommiTeaDesignSystem_20e63b;
|
||||
const { Icon } = DS;
|
||||
const g = window.CT_DATA.gantt;
|
||||
|
||||
const LABELW = 232, DAYW = 21, ROWH = 36, HEADH = 30;
|
||||
const chartW = g.days * DAYW;
|
||||
const W = LABELW + chartW;
|
||||
const H = HEADH + g.rows.length * ROWH;
|
||||
const X = (d) => LABELW + d * DAYW;
|
||||
|
||||
const BAR = {
|
||||
done: { bg: 'var(--paper-3)', border: 'transparent', text: 'var(--ink-3)' },
|
||||
steeping: { bg: 'var(--accent)', border: 'transparent', text: 'var(--ink-inverse)' },
|
||||
review: { bg: 'var(--info-tint)', border: 'var(--info)', text: 'var(--info)' },
|
||||
scheduled: { bg: 'var(--spruce-2)', border: 'var(--spruce-3)', text: 'var(--accent-text)' },
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, flex: 1, minHeight: 0 }}>
|
||||
{/* legend */}
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16, font: '400 11.5px var(--font-mono)', color: 'var(--ink-2)', whiteSpace: 'nowrap', flexWrap: 'wrap' }}>
|
||||
{[['steeping', 'in work'], ['review', 'in review'], ['scheduled', 'scheduled'], ['done', 'done']].map(([k, label]) => (
|
||||
<span key={k} style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 16, height: 9, borderRadius: 3, background: BAR[k].bg, border: `1px solid ${BAR[k].border}`, display: 'inline-block' }}></span>{label}
|
||||
</span>
|
||||
))}
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 18, height: 0, borderTop: '2px dotted var(--cone-line)', display: 'inline-block' }}></span>80% tail
|
||||
</span>
|
||||
<span style={{ display: 'inline-flex', alignItems: 'center', gap: 6 }}>
|
||||
<span style={{ width: 2, height: 12, background: 'var(--jade)', display: 'inline-block' }}></span>today
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* chart */}
|
||||
<div style={{ overflow: 'auto', flex: 1, minHeight: 0, border: '1px solid var(--line-1)', borderRadius: 'var(--radius-3)', background: 'var(--surface-card)' }}>
|
||||
<div style={{ position: 'relative', width: W, height: H }}>
|
||||
{/* week gridlines + labels */}
|
||||
{g.weeks.map((w) => (
|
||||
<React.Fragment key={w.at}>
|
||||
<div style={{ position: 'absolute', left: X(w.at), top: HEADH, bottom: 0, width: 1, background: 'var(--line-1)' }}></div>
|
||||
<span style={{ position: 'absolute', left: X(w.at) + 5, top: 8, font: '400 10.5px var(--font-mono)', color: 'var(--ink-3)', whiteSpace: 'nowrap' }}>{w.label}</span>
|
||||
</React.Fragment>
|
||||
))}
|
||||
{/* milestone 80% band */}
|
||||
<div style={{ position: 'absolute', left: X(g.band.from), width: (g.band.to - g.band.from) * DAYW, top: HEADH, bottom: 0, background: 'var(--cone-fill)' }}></div>
|
||||
<span style={{ position: 'absolute', left: X(g.band.from) + 5, bottom: 6, font: '500 10.5px var(--font-mono)', color: 'var(--cone-line)', whiteSpace: 'nowrap' }}>{g.band.label}</span>
|
||||
{/* due marker */}
|
||||
<div style={{ position: 'absolute', left: X(g.due.at) - 1, top: HEADH, bottom: 0, width: 2, background: 'var(--ink-1)' }}></div>
|
||||
<span style={{ position: 'absolute', left: X(g.due.at) - 4, top: HEADH - 12, width: 8, height: 8, background: 'var(--ink-1)', transform: 'rotate(45deg)' }}></span>
|
||||
{/* today rule */}
|
||||
<div style={{ position: 'absolute', left: X(g.today), top: HEADH, bottom: 0, width: 2, background: 'var(--jade)' }}></div>
|
||||
|
||||
{/* rows */}
|
||||
{g.rows.map((r, i) => {
|
||||
const top = HEADH + i * ROWH;
|
||||
const st = BAR[r.state];
|
||||
return (
|
||||
<React.Fragment key={r.id}>
|
||||
{/* row hairline */}
|
||||
<div style={{ position: 'absolute', left: 0, right: 0, top: top, height: 1, background: 'var(--line-1)', opacity: 0.6 }}></div>
|
||||
{/* label cell (sticky) */}
|
||||
<div
|
||||
onClick={() => onOpenIssue({ id: r.id, title: r.title, labels: [], days: r.state === 'steeping' ? '4d' : undefined })}
|
||||
style={{
|
||||
position: 'absolute', left: 0, width: LABELW, height: ROWH, top: top, zIndex: 2, cursor: 'pointer',
|
||||
display: 'flex', alignItems: 'center', gap: 8, padding: '0 12px 0 14px', background: 'var(--surface-card)',
|
||||
borderRight: '1px solid var(--line-1)',
|
||||
boxShadow: r.crit ? 'inset 2px 0 0 var(--accent)' : 'none',
|
||||
}}
|
||||
>
|
||||
<span style={{ font: '400 11px var(--font-mono)', color: 'var(--ink-3)', flexShrink: 0 }}>#{r.id}</span>
|
||||
<span style={{ font: '500 12px/1.3 var(--font-sans)', color: 'var(--ink-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{r.title}</span>
|
||||
<span style={{ font: '600 8.5px/16px var(--font-sans)', color: 'var(--accent-text)', background: 'var(--spruce-2)', width: 16, height: 16, borderRadius: '50%', textAlign: 'center', flexShrink: 0, marginLeft: 'auto' }}>{r.who}</span>
|
||||
</div>
|
||||
{/* bar */}
|
||||
<div style={{
|
||||
position: 'absolute', left: X(r.start), width: (r.end - r.start) * DAYW, top: top + 9, height: 18,
|
||||
background: st.bg, border: `1px solid ${st.border}`, borderRadius: 4,
|
||||
boxShadow: r.crit && r.state !== 'steeping' ? 'inset 0 -2px 0 var(--accent)' : 'none',
|
||||
}}></div>
|
||||
{/* 80% tail */}
|
||||
{r.p80 ? (
|
||||
<>
|
||||
<div style={{ position: 'absolute', left: X(r.end), width: (r.p80 - r.end) * DAYW, top: top + 17, height: 0, borderTop: '2px dotted var(--cone-line)' }}></div>
|
||||
<div style={{ position: 'absolute', left: X(r.p80) - 1, top: top + 13, width: 2, height: 10, background: 'var(--cone-line)' }}></div>
|
||||
</>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p style={{ font: 'var(--text-agent)', color: 'var(--ink-2)', margin: 0 }}>
|
||||
The path holds if #87 lands by Wednesday. The dotted tails are your own history, wagging.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Object.assign(window, { GanttView });
|
||||
Reference in New Issue
Block a user