memlnaut-nisps/docs/redesign/manifold-export/ui_kits/memlnaut/console/Drawers.jsx
monkey-w1n5t0n 24057e7b5f docs(redesign): Manifold research findings + design specs + build plan
Phase-1 audits (feedback-behaviour, engine-surface, design/Manifold), the
judge-panelled rl-feedback-design, dock/backends/inputs specs, the
upstream-firmware survey, the Manifold design mirror, and BUILD-PLAN.md
(resume anchor + locked decisions).
2026-06-28 04:14:12 +02:00

223 lines
12 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Console 2.0 — Drawer contents. Each renderer takes (ctx, depth) where depth
is 'peek' | 'expand' | 'full'; what shows is gated by depth (the schema-tier
disclosure mechanism, simulated here).
NOTE: these are the PLACEHOLDER drawers. The real dock contents are specified
in docs/redesign/dock-spec.md (six drawers: Learning-Behaviour / Inputs /
Outputs-Routing / Powerful Synth Engine / Particle-Visual / Help). */
const DRW_DS = window.ManifoldDesignSystem_490915;
function Chip({ children, tone }) {
return (
<span style={{
fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.06em',
color: tone || 'var(--fg-mute)', background: 'var(--bg-2)', border: '1px solid var(--line)',
borderRadius: 'var(--r-pill)', padding: '2px 8px',
}}>{children}</span>
);
}
function SectionLabel({ children }) {
return <div style={{ fontSize: 10, color: 'var(--fg-dim)', textTransform: 'uppercase', letterSpacing: '0.1em', marginTop: 'var(--sp-2)' }}>{children}</div>;
}
const PRESETS = ['Sculpt', 'Wander', 'Lock-in', 'Chaos', 'Glide', 'Pin & probe'];
function FeelDrawer(ctx, depth) {
const { ControlAxis, PillToggle } = DRW_DS;
const axes = [
{ key: 'boldness', label: 'Boldness', endpoints: ['Caution', 'Bold'], accent: 'var(--accent)' },
{ key: 'memory', label: 'Memory', endpoints: ['Amnesia', 'Elephant'], accent: 'var(--accent-2)' },
{ key: 'precision', label: 'Precision', endpoints: ['Raw', 'Precise'], accent: 'var(--accent)' },
];
return (
<React.Fragment>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<Chip tone="var(--accent)">3 axes</Chip>
<Chip>preset · {ctx.preset}</Chip>
{ctx.offsetActive && <Chip tone="var(--accent-2)">offset active</Chip>}
</div>
<SectionLabel>Compound axes</SectionLabel>
{axes.map((a) => (
<ControlAxis key={a.key} label={a.label} endpoints={a.endpoints} accent={a.accent}
value={ctx.axes[a.key]} onChange={(v) => ctx.setAxis(a.key, v)}
preset={a.key === 'boldness' ? (ctx.axes.boldness > 0.66 ? 'explore' : 'steady') : undefined} />
))}
<SectionLabel>Control presets</SectionLabel>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
{PRESETS.map((p) => (
<button key={p} type="button" onClick={() => ctx.setPreset(p)}
style={{
padding: '5px 10px', fontSize: 'var(--fs-xs)', fontFamily: 'var(--font-mono)', cursor: 'pointer',
borderRadius: 'var(--r-pill)', border: `1px solid ${ctx.preset === p ? 'var(--accent)' : 'var(--line)'}`,
background: ctx.preset === p ? 'rgba(255,106,0,0.15)' : 'var(--bg-2)',
color: ctx.preset === p ? 'var(--accent)' : 'var(--fg-mute)',
}}>{p}</button>
))}
</div>
{depth !== 'peek' && (
<React.Fragment>
<SectionLabel>Trim-pot offsets</SectionLabel>
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
Axis tweaks layer as offsets over the active preset. Double-tap an axis to re-link. An <span style={{ color: 'var(--accent-2)' }}>offset-active</span> dot marks divergence.
</p>
</React.Fragment>
)}
</React.Fragment>
);
}
function ShapeDrawer(ctx, depth) {
const { Button, Sparkline, Badge } = DRW_DS;
return (
<React.Fragment>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<Chip tone="var(--accent)">examples {ctx.datasetCount}</Chip>
<Chip>loss {ctx.loss.length ? ctx.loss[ctx.loss.length - 1].toExponential(1) : '—'}</Chip>
{ctx.busy && <Chip tone="var(--accent-2)">training</Chip>}
</div>
<SectionLabel>Reinforcement (default)</SectionLabel>
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.6 }}>
Use the + / verdicts on the manifold. Every verdict auto-snapshots first undo is consequence-free.
</p>
<SectionLabel>Examples (IML)</SectionLabel>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<Button size="sm" variant={ctx.addingExample ? 'primary' : 'secondary'} onClick={ctx.onAddExample}>
{ctx.addingExample ? '② store target' : '① add example'}
</Button>
<Button size="sm" onClick={ctx.onTrain} disabled={ctx.busy}>Train</Button>
<Button size="sm" variant="ghost" onClick={ctx.onClear}>clear</Button>
</div>
<SectionLabel>Loss history</SectionLabel>
<Sparkline data={ctx.loss.length ? ctx.loss : [0.5]} log width={depth === 'peek' ? 284 : 480} height={56} />
{depth !== 'peek' && (
<React.Fragment>
<SectionLabel>Snapshot DAG · {ctx.snapshots.length}</SectionLabel>
<ul style={{ listStyle: 'none', margin: 0, padding: 0, display: 'flex', flexDirection: 'column', gap: 4, maxHeight: 160, overflow: 'auto' }}>
{ctx.snapshots.slice().reverse().map((s, idx) => (
<li key={s.id}>
<button type="button" onClick={() => ctx.onJump(s.id)} style={{
width: '100%', textAlign: 'left', display: 'flex', justifyContent: 'space-between',
background: idx === 0 ? 'var(--bg-3)' : 'var(--bg-2)', border: '1px solid var(--line)',
borderRadius: 'var(--r-1)', padding: '4px 8px', cursor: 'pointer', fontFamily: 'var(--font-mono)', fontSize: 'var(--fs-xs)',
}}>
<span style={{ color: idx === 0 ? 'var(--accent)' : 'var(--fg)' }}>{s.tag}</span>
<span style={{ color: 'var(--fg-dim)' }}>noise {s.noise.toFixed(2)}</span>
</button>
</li>
))}
</ul>
</React.Fragment>
)}
</React.Fragment>
);
}
const STATUS_CYCLE = { off: 'fixed', fixed: 'live', live: 'off' };
const STATUS_COLOR = { off: 'var(--fg-dim)', fixed: 'var(--accent-2)', live: 'var(--accent)' };
function RouteDrawer(ctx, depth) {
const { PillToggle, Badge } = DRW_DS;
const counts = ctx.params.reduce((a, p) => { a[p.status] = (a[p.status] || 0) + 1; return a; }, {});
const rows = depth === 'peek' ? ctx.params.slice(0, 6) : ctx.params;
return (
<React.Fragment>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
<Chip>sources {ctx.mode.inN}/48</Chip>
<Chip>targets {ctx.params.length}/{ctx.params.length}</Chip>
<Chip tone="var(--accent)">live {counts.live || 0}</Chip>
<Chip tone="var(--accent-2)">fixed {counts.fixed || 0}</Chip>
</div>
<SectionLabel>Input source</SectionLabel>
<Badge tone="info">{ctx.mode.input === 'joystick' ? 'joystick' : ctx.mode.input === 'audio_in' ? 'mic (1-input)' : 'xy pad'}</Badge>
<SectionLabel>Output backend</SectionLabel>
<PillToggle value={ctx.outputBackend} onChange={ctx.setOutputBackend}
options={[{ value: 'audio', label: 'Audio' }, { value: 'midi', label: 'MIDI' }, { value: 'osc', label: 'OSC' }, { value: 'cv', label: 'CV' }]} />
<SectionLabel>Control points · off / fixed / live</SectionLabel>
<div style={{ display: 'flex', flexDirection: 'column', gap: 3, maxHeight: depth === 'peek' ? 150 : 320, overflow: 'auto' }}>
{rows.map((p, i) => (
<div key={i} style={{ display: 'flex', alignItems: 'center', gap: 'var(--sp-2)', background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 'var(--r-1)', padding: '4px 8px' }}>
<span style={{ flex: 1, fontSize: 'var(--fs-xs)', color: 'var(--fg)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{p.name}</span>
<span style={{ fontSize: 10, color: 'var(--fg-dim)' }}>{p.group}</span>
<button type="button" onClick={() => ctx.cycleStatus(ctx.params.indexOf(p))}
style={{ width: 52, fontSize: 10, fontFamily: 'var(--font-mono)', textTransform: 'uppercase', letterSpacing: '0.06em',
border: `1px solid ${STATUS_COLOR[p.status]}`, color: STATUS_COLOR[p.status], background: 'transparent',
borderRadius: 'var(--r-1)', padding: '2px 4px', cursor: 'pointer' }}>{p.status}</button>
</div>
))}
</div>
{depth === 'full' && (
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: '8px 0 0', lineHeight: 1.6 }}>
Full tier hosts the Emitters × Targets matrix + per-cell editor + staged-unlock opt-in (built for the modular mega-mode).
</p>
)}
</React.Fragment>
);
}
function GradientBars({ norms, status }) {
const COLOR = { vanishing: 'var(--info)', exploding: 'var(--danger)', converged: 'var(--fg-mute)', healthy: 'var(--good)' };
const max = Math.max(...norms, 0.0001);
return (
<div style={{ display: 'flex', alignItems: 'flex-end', gap: 3, height: 56, background: 'var(--bg-1)', border: '1px solid var(--line)', borderRadius: 'var(--r-2)', padding: 6 }}>
{norms.map((n, i) => (
<div key={i} style={{ flex: 1, display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 2, height: '100%', justifyContent: 'flex-end' }}>
<div style={{ width: '100%', height: `${(n / max) * 100}%`, background: COLOR[status[i]] || 'var(--good)', borderRadius: 1 }} />
<span style={{ fontSize: 8, color: 'var(--fg-dim)' }}>L{i}</span>
</div>
))}
</div>
);
}
function HealthDrawer(ctx, depth) {
const { Sparkline, Switch, Slider, Badge } = DRW_DS;
const healthTone = ctx.health > 0.66 ? 'good' : ctx.health > 0.33 ? 'warn' : 'bad';
return (
<React.Fragment>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', alignItems: 'center' }}>
<Badge tone={healthTone} dot>{healthTone === 'good' ? 'healthy' : healthTone === 'warn' ? 'drifting' : 'unstable'}</Badge>
<Chip>rev {ctx.weightsRevision}</Chip>
</div>
<SectionLabel>Loss history</SectionLabel>
<Sparkline data={ctx.loss.length ? ctx.loss : [0.5]} log width={depth === 'peek' ? 284 : 480} height={56} />
<SectionLabel>Gradient flow · per layer</SectionLabel>
<GradientBars norms={ctx.gradient} status={ctx.gradientStatus} />
{depth !== 'peek' && (
<React.Fragment>
<SectionLabel>Lab controls</SectionLabel>
<Switch checked={ctx.spread} onChange={ctx.setSpread} label="spread (Xavier regime)" />
<Slider label="tame · output limiter" value={ctx.tame} min={0} max={1} step={0.01} onChange={ctx.setTame} />
<Slider label="noise cap" value={ctx.noiseCap} min={0} max={0.5} step={0.01} onChange={ctx.setNoiseCap} />
</React.Fragment>
)}
</React.Fragment>
);
}
const KEYS = [['15', 'open drawers'], ['\\', 'full depth'], ['space / ↑', 'commit +'], ['↓', 'perturb '], ['z', 'undo'], ['double-click dot', 'follow mode']];
function HelpDrawer(ctx, depth) {
return (
<React.Fragment>
<SectionLabel>Keyboard</SectionLabel>
<div style={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{KEYS.map(([k, v]) => (
<div key={k} style={{ display: 'flex', justifyContent: 'space-between', fontSize: 'var(--fs-xs)' }}>
<kbd style={{ background: 'var(--bg-2)', border: '1px solid var(--line)', borderRadius: 'var(--r-1)', padding: '1px 6px', color: 'var(--accent)' }}>{k}</kbd>
<span style={{ color: 'var(--fg-mute)' }}>{v}</span>
</div>
))}
</div>
<SectionLabel>The loop</SectionLabel>
<p style={{ fontSize: 'var(--fs-xs)', color: 'var(--fg-dim)', margin: 0, lineHeight: 1.7 }}>
Drag the manifold to explore. Hear something good + to settle. Wrong to perturb. Went too far undo. The screen reveals exactly as much machinery as you reach for.
</p>
</React.Fragment>
);
}
window.DRAWERS = {
shape: { icon: '◇', label: 'Shape', render: ShapeDrawer },
feel: { icon: '◎', label: 'Feel', render: FeelDrawer },
route: { icon: '⇄', label: 'Route', render: RouteDrawer },
health: { icon: '♥', label: 'Health', render: HealthDrawer },
help: { icon: '?', label: 'Help', render: HelpDrawer },
};