/** * Console — the FIVE real dock drawers (operator dock restructure). Each renderer * takes (ctx, depth); what shows is gated by depth (condensed | expanded): * * learn — Learning : feedback-mode selector, solo/arm chooser, live params * inputs — Inputs : input source * route — Outputs : per-output control matrix for the ACTIVE mode/backend, * a Mode-specific config section, and (Editor mode) the * MEMLNaut serial panel. The old separate "Synth" and * "Particle/Visual" drawers are REMOVED — their config now * lives here under the active Mode (TOP dock selector). * settings — Settings : icon style + input-map shape (settings-store) * help — Help : keymap + the loop explanation * * The TOP dock selector ("Mode") chooses the active OUTPUT backend/target; this * drawer renders whatever that backend needs. * * Engine wiring: the feedback-mode pill → controller.setMode; the arm flags → * engine.feedback.setFocus; per-output rows write the shared MFParam store. * Where engine support does not yet exist the UI + state are wired and a TODO * references the relevant spec — no faked engine behaviour. */ import type { ReactNode } from 'react'; import { Badge, Button, PillToggle, Slider, Switch } from '../primitives'; import type { ConsoleCtx, DrawerDepth, DrawerKey, FeedbackModeUI, SoloMode } from './types'; import type { InputMode } from '../inputs'; import { OutputControlRow } from '../dock/OutputControlRow'; import { BackendAdvanced } from '../dock/BackendAdvanced'; import { OutputsBackendConfig, BackendStatusChip } from '../dock/OutputsBackendConfig'; import { BACKENDS } from '../dock/output-state'; import { shapeValues } from './model'; import { outputModeDescriptor } from './output-mode'; import { useSettings, unfocusedIconCss } from '../settings/settings-store'; import type { UnfocusedIconColour, InputMapMode } from '../settings/settings-store'; import { EditorPanel } from '../serial/EditorPanel'; import { LearningIcon, InputsIcon, OutputsIcon, SettingsIcon, HelpIcon, } from './icons'; function Chip({ children, tone }: { children: ReactNode; tone?: string }) { return ( {children} ); } function SectionLabel({ children }: { children: ReactNode }) { return (
{FEEDBACK_DESC[ctx.feedbackMode]}
)} {depth === 'expanded' && ( <>{SOLO_DESC[ctx.soloMode]} Solo only freezes the rest as far as a shared network allows.
State machine: idle → exploring → commit / cancel. While Explore & place is exploring, training is paused and the joystick auditions a random scratchpad net; + commits a placed anchor and restores the real net.
Loss plot · weight-health · layer-stats · gradient-flow land here once the loss-history C API is plumbed (dock-spec §1.3).
> )} > ); } // =========================================================================== // 2. INPUTS (workstream F; inputs-spec — modular input layer) // =========================================================================== const STATUS_TONE: RecordDrag the on-screen manifold / XY pad. Two axes feed the net directly — this is the default.
)} {/* ---- Game Controller ---- */} {inp.inputMode === 'gamepad' && depth === 'expanded' && ( <>Connect a controller and press any button to wake it. Sticks drive the input map; buttons fire the verdicts above.
> )} {/* ---- MIDI ---- */} {inp.inputMode === 'midi' && depth === 'expanded' && ( <>No MIDI inputs detected. Connect a device — it appears here automatically.
) : ({/* TODO(workstream F, docs/redesign/inputs-spec.md — "multiple WASM modules + warm-start"): give every axis its own genuine input dimension by (re)loading a WASM module whose MLP arity matches axisCount and warm-starting from the prior net. Deferred — the reduction lives in InputLayer.compose(). */} The browser WASM is a fixed {inp.engineInputSize}-input head (MLP<2,…>), so the{' '} {inp.axisCount} axes are blended down to {inp.engineInputSize} (even→X / odd→Y mean). True per-axis dimensions land with the multi-WASM reshape.
)} > ); } // =========================================================================== // 3. OUTPUTS / ROUTING (dock-spec §3, §4) // =========================================================================== const VISUAL_NAMES = [ 'Flow', 'Scale', 'Speed', 'Hue', 'Spread', 'Size', 'Trail', 'Turb', 'Attract', 'Radius', 'DispRate', 'DispAmt', 'Lifetime', 'Respawn', 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate', ]; /** * Per-Mode config section shown ABOVE the per-output rows. The synth Mode shows * transport + tempo; the particle Mode names the outputs; MIDI/OSC/Editor show * their own affordances. Replaces the removed Synth + Visual drawers. */ function ModeConfig(ctx: ConsoleCtx, depth: DrawerDepth) { switch (ctx.outputMode) { case 'synth': return ( <>The active engine follows the selected mode ({ctx.mode.label}). {/* TODO(dock-spec §5): arpeggiator + tiered synth presets + the 18-section group-override matrix are workstream E. */}
> )} > ); case 'editor': return ( <>Flow-field visualiser driven by the first {Math.min(20, ctx.params.length)} outputs (no audio). {/* TODO(backends-spec §4): port FlowFieldVisualizer + visual preset chips. */}
) : null; case 'midi': // The full MIDI config (port picker, CC count, per-output CC/channel/name) // + preset bar render via OutputsBackendConfig in RoutingDrawer below. return depth === 'expanded' ? (Each output sends a real Web MIDI CC. Pick a port and set CC# / channel per output.
) : null; case 'osc': return depth === 'expanded' ? (Each output sends to an OSC path with a physical range, over the WebSocket bridge.
) : null; default: return null; } } function RoutingDrawer(ctx: ConsoleCtx, depth: DrawerDepth) { const values = shapeValues(ctx.params, null); // bar uses shaped held/live value snapshot const counts = ctx.params.reduceFocused / active icons are always accent orange. This sets the resting colour of unfocused icons (preview below).
The 2D input surface: a rectangular XY map or a circular joystick-style disc. "Follow mode" uses the active mode's declared input (joystick → circular, else rectangular).
)}Roundness of buttons, control rows, dock icons and panels. Pills and the circular verdict buttons are intentionally exempt. Default 2px.
)} > ); } // =========================================================================== // 5. HELP // =========================================================================== const KEYS: [string, string][] = [ ['1–5', 'open drawers'], ['\\', 'expand drawer'], ['space / ↑', 'commit +'], ['↓', 'perturb / down −'], ['z', 'undo'], ['[ ] =', 'split (composite)'], ]; function HelpDrawer() { return ( <>Drag the manifold to explore. Hear something good → + to keep it. Wrong → − to push away or re-roll (set the behaviour in the Learning drawer). Went too far → undo. The dock reveals exactly as much machinery as you reach for.