diff --git a/manifold/src/App.tsx b/manifold/src/App.tsx index a148069..f9325c0 100644 --- a/manifold/src/App.tsx +++ b/manifold/src/App.tsx @@ -1,8 +1,9 @@ /** * Manifold — app root. The convertible Console (ConsoleApp) wired to the real - * engine, mounted under EngineProvider. Defaults to the hero `focus="composite"` - * (the convertible centerpiece). The `?debug=1` probe is installed once the - * engine is live. + * engine, mounted under EngineProvider. ConsoleApp renders its single + * "composite" stage unconditionally (the dead focus/altitude system was + * deleted 2026-07 — simplification audit S15). The `?debug=1` probe is + * installed once the engine is live. */ import { useEffect } from 'react'; @@ -75,7 +76,7 @@ export function App() { return ( }> - + ); } diff --git a/manifold/src/console/ConsoleApp.tsx b/manifold/src/console/ConsoleApp.tsx index 0290814..6149ef0 100644 --- a/manifold/src/console/ConsoleApp.tsx +++ b/manifold/src/console/ConsoleApp.tsx @@ -14,46 +14,46 @@ * FeedbackMode::ExploreAndPlace (set on mount; the controller forwards the * Idle→Exploring→Placing lifecycle to engine.feedback.* — nisps/ml/feedback.hpp, * per docs/adr/rl-feedback-design.md). - * - AltitudeNav switches `focus` via React state (in|split|out|composite), not - * by navigating to separate HTML files. * - `c15` is labelled "Powerful Synth Engine" (in model.ts) — "C15" never shows. * - * UI-only state (params status/min/max/curve, snapshots, A/B seed, axes, - * noiseCap, health/rev visuals) is preserved as faithful local React state. + * The dead focus/altitude system (AltitudeNav; SplitStage/ReadoutStrip/InputMini + * stages; the `in`/`split`/`out` branches) was deleted 2026-07 (simplification + * audit S15) — `setFocus` was never called anywhere, so `composite` was the + * only reachable stage. The decorative A/B toggle, fake seed, snapshot stack, + * fabricated health/gradient visuals, and the never-touched master + * volume/bpm/learning-rate/decay/spread-level sliders were deleted alongside it + * (S16/L1) — each was write-only, read by nothing but its own control. + * + * UI-only state (params status/min/max/curve, noiseCap) is preserved as + * faithful local React state. */ import { useEffect, useMemo, useRef, useState } from 'react'; import type { CSSProperties } from 'react'; import { useEngine, useEngineVersion, ExplorationController } from '../engine'; -import { MF_MODES, modeEngineId, seededGradient, shapeValues } from './model'; +import { MF_MODES, modeEngineId, shapeValues } from './model'; import type { MFParam } from './model'; import { CompositeStage } from './CompositeStage'; import { ParticleStage } from './ParticleStage'; import { SandwichStage } from './SandwichStage'; -import { SplitStage } from './SplitStage'; import { OutputStage } from './OutputStage'; -import { InputMini } from './InputMini'; import { Manifold } from './Manifold'; -import { ReadoutStrip } from './ReadoutStrip'; import { VerdictCluster } from './VerdictCluster'; import { Dock } from './Dock'; import { ReshapeModal } from './ReshapeModal'; import type { - Axes, ConsoleCtx, DrawerDepth, DrawerKey, FeedbackMarker, FeedbackModeUI, - Focus, OutputMode, Pin, - Snapshot, SoloMode, } from './types'; import type { BackendId } from '../dock/output-state'; import { buildArmMask } from '../dock/output-state'; import { FeedbackController, type ProtoFeedbackMode } from '../feedback'; -import { DEFAULT_OUTPUT_MODE, OUTPUT_MODES, outputModeDescriptor } from './output-mode'; +import { DEFAULT_OUTPUT_MODE, outputModeDescriptor } from './output-mode'; import { useSettings, resolveInputMap } from '../settings/settings-store'; import { useBackendManager } from '../backends'; import { useInputLayer } from '../inputs'; @@ -77,8 +77,6 @@ declare global { } } -let SNAP_ID = 0; - /** Small pill-button style for the exploring-scratchpad banner controls. */ function pillBtn(color: string): CSSProperties { return { @@ -93,39 +91,22 @@ function pillBtn(color: string): CSSProperties { }; } -export interface ConsoleAppProps { - focus?: Focus; -} - -export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProps) { +export function ConsoleApp() { const engine = useEngine(); const version = useEngineVersion(engine); const { settings } = useSettings(); - const [focus, setFocus] = useState(initialFocus); const [modeId, setModeId] = useState('paf_synth'); const mode = MF_MODES.find((m) => m.id === modeId) ?? MF_MODES[0]; const [params, setParams] = useState(() => mode.params.map((p) => ({ ...p }))); const [pos, setPos] = useState<[number, number]>([0.5, 0.5]); - // A/B seed-snapshot model (kept as visual parity; A holds a remembered weight - // snapshot conceptually — here we mirror the JSX's seed-based preview marker). - const [seed, setSeed] = useState(0.4); - const [axes, setAxes] = useState({ boldness: 0.55, memory: 0.4, precision: 0.5 }); - const [preset, setPreset] = useState('Sculpt'); const [noiseCap, setNoiseCap] = useState(0.12); const [examples, setExamples] = useState(0); const [addingExample, setAddingExample] = useState(false); const [loss, setLoss] = useState([]); const [busy, setBusy] = useState(false); - const [snapshots, setSnapshots] = useState([]); - const [ab, setAB] = useState<'A' | 'B'>('B'); - const [, setHoldingA] = useState(false); - const aRef = useRef<{ seed: number } | null>(null); const [spread, setSpread] = useState(false); - const [tame, setTame] = useState(0.85); - const [health, setHealth] = useState(0.8); - const [rev, setRev] = useState(1); const [active, setActive] = useState(null); const [depth, setDepth] = useState('condensed'); // Sandwich (parameter-landscape) centre-stage toggle — dock-bottom layers icon. @@ -146,9 +127,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp const [picking, setPicking] = useState(false); const [anchorCount, setAnchorCount] = useState(0); const [undoDepth, setUndoDepth] = useState(0); - const [learningRate, setLearningRate] = useState(0.00001); - const [decay, setDecay] = useState(0.97); - const [spreadLevel, setSpreadLevel] = useState(0.6); // Exploration gestures (Jolt held weight-morph + OU explore-intensity). The // maths lives in the ExplorationController (engine/exploration.ts); these are // the React-visible reflections the Learning drawer renders. @@ -170,8 +148,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp const [vcvSendRaw, setVcvSendRaw] = useState(false); // Feedback markers plotted on the 2D map (both polarities; session-scoped). const [markers, setMarkers] = useState([]); - const [volume, setVolume] = useState(0.8); - const [bpm, setBpm] = useState(120); const [audioStarted, setAudioStarted] = useState(false); const [follow, setFollow] = useState(false); const [split, setSplit] = useState(() => { @@ -181,7 +157,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp useEffect(() => { localStorage.setItem('mf-composite-split', String(split)); }, [split]); - const [stripPinned, setStripPinned] = useState(true); const [firstSession, setFirstSession] = useState(true); const [pins, setPins] = useState([]); @@ -192,7 +167,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp const controllerRef = useRef(null); if (engine && !controllerRef.current) { controllerRef.current = new FeedbackController(engine, { - seed: 0xfeedbacc, spread: 0.6, }); } @@ -289,8 +263,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp setPos([0.5, 0.5]); setExamples(0); setLoss([]); - setSnapshots([]); - setSeed(0.4); setFollow(false); setPins([]); setMarkers([]); @@ -392,10 +364,8 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp if (!c || !c.isPicking()) return; c.placeCommit(x, y); setPos([x, y]); - pushSnap('anchor'); pushMarker([x, y], 'positive'); syncController(); - setRev((r) => r + 1); }; // Output backend transport (backends-spec). The manager consumes the engine @@ -441,11 +411,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp [params, version, engine], ); - const gradient = useMemo(() => seededGradient(rev), [rev]); - - const pushSnap = (tag: string) => - setSnapshots((s) => [...s, { id: ++SNAP_ID, tag, noise: noiseCap, seed }].slice(-50)); - /** Plot a feedback marker at the input location it was given (session-scoped). */ const pushMarker = (at: [number, number], polarity: 'positive' | 'negative') => setMarkers((m) => [...m, { x: at[0], y: at[1], polarity }].slice(-200)); @@ -473,17 +438,14 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp if (feedbackMode === 'explore-and-place') { if (c?.getState().exploring) { // Place the current candidate → next manifold tap chooses the location. - pushSnap('place'); c.place(); } else { // Not exploring: a plain positive reinforcement of the current mapping. - pushSnap('commit +'); c?.like(pos, engine?.getOutputs() ?? new Float32Array(0)); pushMarker(pos, 'positive'); } } else { // Geometric dislike: thumbs-up = like + train. - pushSnap('like +'); c?.like(pos, engine?.getOutputs() ?? new Float32Array(0)); pushMarker(pos, 'positive'); } @@ -491,8 +453,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp forwardVcvFeedback('up'); syncController(); setNoiseCap((n) => Math.max(0.02, n * 0.7)); - setHealth((h) => Math.min(1, h + 0.08)); - setRev((r) => r + 1); const l = engine?.evalLoss(); setLoss((prev) => [...prev, Number.isFinite(l) ? (l as number) : prev.length ? prev[prev.length - 1] : 0.5].slice(-120), @@ -508,10 +468,8 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp // Enter the scratchpad (or, if already exploring, cancel back to the real // net). NEVER a dislike — Mode 2 is positive-only. if (c?.getState().exploring) { - pushSnap('cancel explore'); c.cancel(); } else { - pushSnap('explore'); c?.enterExplore(); // VCV bridged mode: entering explore re-rolls the module's net too. forwardVcvFeedback('rand'); @@ -520,31 +478,21 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp // Geometric dislike: push the current mapping away from this sound. Pass the // HEARD (post-pipeline, routed) vector — NOT the raw MLP output — so the // core has a non-zero MSE derivative (see engine.feedback.dislikeGeometric). - pushSnap('dislike −'); - const action = c?.dislike( - pos, - engine?.routedOutput() ?? new Float32Array(0), - noiseCap, - spread ? 1 : 0.6, - ); + const action = c?.dislike(engine?.routedOutput() ?? new Float32Array(0)); // GeometricColdStart (15): no positives yet → show the one-time prompt. if (action === 15) setColdStart(true); pushMarker(pos, 'negative'); // VCV bridged mode: thumbs-down = negative verdict. forwardVcvFeedback('down'); - setSeed((s) => s + (Math.random() - 0.5) * (noiseCap * 4 + 0.3)); setNoiseCap((n) => Math.min(0.5, n + 0.06)); - setHealth((h) => Math.max(0.1, h - 0.06)); } syncController(); - setRev((r) => r + 1); }; /** Long-press perturb / explicit re-roll. */ const reroll = () => { const c = controllerRef.current; setFirstSession(false); - pushSnap('re-roll'); if (feedbackMode === 'explore-and-place' && c?.getState().exploring) { // Re-roll the scratchpad net (undoable) without leaving the session. c.reroll(); @@ -555,10 +503,7 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp // VCV bridged mode: re-roll the module's net too. forwardVcvFeedback('rand'); syncController(); - setSeed(Math.random() * 6); setNoiseCap(0.4); - setHealth(0.5); - setRev((r) => r + 1); }; /** @@ -575,51 +520,40 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp engine?.process(); } syncController(); - setRev((r) => r + 1); }; /** - * Undo. While exploring (Mode 2) this pops the scratchpad undo ring (reroll / - * nudge). Otherwise it falls back to the UI snapshot stack (visual A/B seed). + * Undo. Only meaningful while exploring (Mode 2) — pops the scratchpad undo + * ring (reroll / nudge), which is real, engine-backed undo. Geometric dislike + * (Mode 1) and the non-exploring case have no real undo in the core, so the + * button is simply inactive there (the prior "undo" outside a scratchpad + * session only reverted a decorative UI snapshot stack — deleted 2026-07, + * simplification audit S16). */ const undo = () => { const c = controllerRef.current; if (feedbackMode === 'explore-and-place' && c?.getState().exploring) { c.undo(); syncController(); - setRev((r) => r + 1); - return; } - setSnapshots((s) => { - if (!s.length) return s; - const last = s[s.length - 1]; - setSeed(last.seed); - setNoiseCap(last.noise); - setRev((r) => r + 1); - return s.slice(0, -1); - }); }; // ---- Explore-and-place scratchpad ops surfaced to the dock + cluster ---- const onExplore = () => { controllerRef.current?.enterExplore(); syncController(); - setRev((r) => r + 1); }; const onScratchReroll = () => { controllerRef.current?.reroll(); syncController(); - setRev((r) => r + 1); }; const onScratchNudge = () => { controllerRef.current?.nudge(); syncController(); - setRev((r) => r + 1); }; const onScratchUndo = () => { controllerRef.current?.undo(); syncController(); - setRev((r) => r + 1); }; const onPlace = () => { controllerRef.current?.place(); @@ -629,13 +563,11 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp setBusy(true); controllerRef.current?.finalise(); syncController(); - setRev((r) => r + 1); setBusy(false); }; const onCancelExplore = () => { controllerRef.current?.cancel(); syncController(); - setRev((r) => r + 1); }; const train = () => { setBusy(true); @@ -655,30 +587,11 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp // Snapshot the current input → current (shaped) output as a training example. engine?.addExample([pos[0], pos[1]], Array.from(values)); setExamples((e) => e + 1); - pushSnap('example'); train(); }; const setParam = (i: number, patch: Partial) => setParams((ps) => ps.map((p, j) => (j === i ? { ...p, ...patch } : p))); - const cycleStatus = (i: number) => - setParams((ps) => - ps.map((p, j) => - j === i - ? { ...p, status: ({ off: 'fixed', fixed: 'live', live: 'off' } as const)[p.status] } - : p, - ), - ); - - const toggleAB = () => { - if (ab === 'B') { - aRef.current = { seed }; - setAB('A'); - } else { - if (aRef.current) setSeed(aRef.current.seed); - setAB('B'); - } - }; // keyboard accelerators useEffect(() => { @@ -702,13 +615,13 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp setActive((a) => (a === map[e.key] ? null : map[e.key])); setDepth('condensed'); } else if (e.key === '\\') setDepth((d) => (d === 'expanded' ? 'condensed' : 'expanded')); - else if (focus === 'composite' && e.key === '[') { + else if (e.key === '[') { e.preventDefault(); setSplit((s) => Math.max(0, s - 0.04)); - } else if (focus === 'composite' && e.key === ']') { + } else if (e.key === ']') { e.preventDefault(); setSplit((s) => Math.min(1, s + 0.04)); - } else if (focus === 'composite' && (e.key === '=' || e.key === '0')) { + } else if (e.key === '=' || e.key === '0') { e.preventDefault(); setSplit(0.5); } else if (e.key === ' ' || e.key === 'ArrowUp') { @@ -791,11 +704,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp modeId, setModeId, mode, - axes, - setAxis: (k, v) => setAxes((s) => ({ ...s, [k]: v })), - preset, - setPreset, - offsetActive: preset !== 'Sculpt', datasetCount: examples, loss, busy, @@ -812,25 +720,8 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp setMarkers([]); setPins([]); }, - snapshots, - onJump: (id) => { - const s = snapshots.find((x) => x.id === id); - if (s) { - setSeed(s.seed); - setNoiseCap(s.noise); - setRev((r) => r + 1); - } - }, params, - cycleStatus, setParam, - outputBackend, - setOutputBackend: (b) => { - // Map a backend id back onto the active Mode (the Mode is the source of - // truth; the Outputs drawer drives it via setOutputMode). - const m = OUTPUT_MODES.find((om) => om.backend === b); - if (m) setOutputMode(m.id); - }, outputMode, setOutputMode, // ---- output backend transport ---- @@ -853,16 +744,9 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp cvIdentify, cvDisconnect, setParams: (next: MFParam[]) => setParams(next), - markers, inputs, - health, - gradient: gradient.norms, - gradientStatus: gradient.status, - weightsRevision: rev, spread, setSpread, - tame, - setTame, noiseCap, setNoiseCap, // learning-behaviour @@ -874,12 +758,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp learningPaused, armedCount: params.filter((p) => p.armed).length, clearArmed: () => setParams((ps) => ps.map((p) => (p.armed ? { ...p, armed: false } : p))), - learningRate, - setLearningRate, - decay, - setDecay, - spreadLevel, - setSpreadLevel, // exploration gestures (Jolt + OU explore) joltActive, onJoltPress, @@ -889,10 +767,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp // synth audioStarted, onToggleAudio, - volume, - setVolume, - bpm, - setBpm, // explore-and-place scratchpad session (workstream B) picking, anchorCount, @@ -909,8 +783,6 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp // Resolve the effective input-map shape from Settings + the mode's declared input. const inputMapVariant = resolveInputMap(settings.inputMap, mode.input); - const healthColor = - health > 0.66 ? 'rgba(107,194,107,' : health > 0.33 ? 'rgba(245,196,94,' : 'rgba(255,68,102,'; const addPin = (p: [number, number]) => setPins((ps) => [...ps, { x: p[0], y: p[1], color: 'rgba(255,106,0,0.16)' }]); @@ -926,41 +798,9 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp > - {/* ambient health glow at the screen edge */} -
- {/* stage = manifold area (left of dock) */}
- {focus === 'in' && (stripPinned || mode.cls !== 'Synth') && ( -
- setStripPinned((p) => !p)} - /> -
- )} - -
+
{sandwich ? ( // Sandwich centre-stage: shrunken input (left) · landscape stack // (centre, fills) · compact outputs (right). Replaces the Mode stage. @@ -1011,7 +851,7 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp
) : outputMode === 'particles' ? ( - ) : focus === 'composite' ? ( + ) : ( - ) : focus === 'split' ? ( - - ) : focus === 'out' ? ( - <> - - - - ) : ( - )} {/* corner overlay — hidden in Particle mode (top axis bar owns that row) */} @@ -1092,10 +892,7 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp onReroll={reroll} onNudge={nudgeNet} onRandomise={reroll} - canUndo={feedbackMode === 'explore-and-place' && exploring ? undoDepth > 0 : snapshots.length > 0} - ab={ab} - onToggleAB={toggleAB} - onHoldA={setHoldingA} + canUndo={feedbackMode === 'explore-and-place' && exploring && undoDepth > 0} firstSession={firstSession} feedbackMode={feedbackMode} exploring={exploring} @@ -1205,11 +1002,10 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp
)} - {/* Global PICK-LOCATION capture overlay: works in any focus (composite/ - split stages don't expose picking). The directly-rendered Manifold - (focus==='in') also handles picks + draws the reticle; this overlay - guarantees the place→pick loop is reachable everywhere. */} - {picking && focus !== 'in' && ( + {/* Global PICK-LOCATION capture overlay: the CompositeStage/ParticleStage + don't expose picking directly, so this transparent overlay captures + the pointer-down and routes it to onPickLocation everywhere. */} + {picking && (
{ const r = e.currentTarget.getBoundingClientRect(); diff --git a/manifold/src/console/Drawers.tsx b/manifold/src/console/Drawers.tsx index d134e05..c91f3c2 100644 --- a/manifold/src/console/Drawers.tsx +++ b/manifold/src/console/Drawers.tsx @@ -25,9 +25,7 @@ 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'; @@ -139,6 +137,13 @@ const FEEDBACK_DESC: Record = { 'explore-and-place': 'Down re-rolls the whole net into a scratchpad you audition; + places a liked sound (Mode 2).', }; +/** + * Solo behaviour. `mask-gradients` is the only variant the core actually + * implements — `soloMode` has no other observable effect today (the other two + * options need the C API's `train_masked` step). Rendered as a fixed, + * non-selectable label rather than a picker that offers dead choices + * (simplification audit L20). + */ const SOLO_OPTS: { value: SoloMode; label: string }[] = [ { value: 'mask-gradients', label: 'Mask gradients' }, { value: 'zero-loss', label: 'Zero loss' }, @@ -234,25 +239,14 @@ function LearningDrawer(ctx: ConsoleCtx, depth: DrawerDepth) {
Solo behaviour - + {SOLO_OPTS.find((o) => o.value === ctx.soloMode)?.label}

{SOLO_DESC[ctx.soloMode]} Solo only freezes the rest as far as a shared network allows. + The other two behaviours need real core support (`train_masked`) and aren't selectable yet.

Live training params - - - v.toExponential(1)} - /> - )} @@ -569,17 +563,14 @@ function ModeConfig(ctx: ConsoleCtx, depth: DrawerDepth) { audio starts on the play gesture
- {depth === 'expanded' && ( - <> - Tempo - -

- 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. */} -

- +

+ 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. Master + volume + bpm sliders were deleted (simplification audit + S16) — they drove no engine parameter. */} +

)} ); @@ -624,7 +615,6 @@ function RoutingDrawer(ctx: ConsoleCtx, depth: DrawerDepth) { }, {}); const mutedN = ctx.params.filter((p) => p.muted).length; const modeDesc = outputModeDescriptor(ctx.outputMode); - const backend = BACKENDS.find((b) => b.id === modeDesc.backend) ?? BACKENDS[0]; // The particle Mode names its outputs; otherwise use the param names. const nameFor = (idx: number, fallback: string) => ctx.outputMode === 'particles' ? VISUAL_NAMES[idx] ?? fallback : fallback; @@ -671,12 +661,6 @@ function RoutingDrawer(ctx: ConsoleCtx, depth: DrawerDepth) { {!expanded && ctx.params.length > 6 && ( +{ctx.params.length - 6} more — expand to edit )} - {expanded && ( - <> - Advanced · {modeDesc.label} - - - )} ); } diff --git a/manifold/src/console/InputMini.tsx b/manifold/src/console/InputMini.tsx deleted file mode 100644 index 67c6b36..0000000 --- a/manifold/src/console/InputMini.tsx +++ /dev/null @@ -1,81 +0,0 @@ -/** - * InputMini — the input demoted to a compact secondary control for output-first - * views. Ported from `InputMini.jsx`. - */ -import { VirtualJoystick, XYPad } from '../primitives'; -import type { MFMode } from './model'; - -export interface InputMiniProps { - mode: MFMode; - pos: [number, number]; - onMove: (x: number, y: number) => void; - noiseCap?: number; - size?: number; - corner?: 'bottom-left' | 'bottom-right' | 'top-left'; - /** Input-map shape override (Settings). Falls back to the mode's input. */ - variant?: 'rectangular' | 'circular'; -} - -export function InputMini({ - mode, - pos, - onMove, - size = 132, - corner = 'bottom-left', - variant, -}: InputMiniProps) { - const circular = variant ? variant === 'circular' : mode.input === 'joystick'; - const place = { - 'bottom-left': { bottom: 14, left: 14 }, - 'bottom-right': { bottom: 14, right: 14 }, - 'top-left': { top: 14, left: 14 }, - }[corner]; - return ( -
-
- - input · {circular ? 'joy' : 'xy'} - - - {pos[0].toFixed(2)},{pos[1].toFixed(2)} - -
- {circular ? ( - onMove(x, y)} /> - ) : ( - onMove(x, y)} showGrid /> - )} -
- ); -} diff --git a/manifold/src/console/ReadoutStrip.tsx b/manifold/src/console/ReadoutStrip.tsx deleted file mode 100644 index bc16239..0000000 --- a/manifold/src/console/ReadoutStrip.tsx +++ /dev/null @@ -1,219 +0,0 @@ -/** - * ReadoutStrip — the output heatmap as a thin top control strip. Same model as - * OutputStage. Ported from `ReadoutStrip.jsx`. - */ -import { useRef, useState } from 'react'; -import type { PointerEvent as ReactPointerEvent } from 'react'; -import type { MFParam, ParamStatus } from './model'; -import { OutputEditor } from './OutputEditor'; - -const RS_GROUP_COLOR: Record = { - formant: '--accent', - pitch: '--accent-2', - amp: '--good', - filter: '--warn', - fx: '--info', - mod: '--accent-3', -}; -const RS_NEXT: Record = { off: 'fixed', fixed: 'live', live: 'off' }; - -export interface ReadoutStripProps { - params: MFParam[]; - values: number[]; - onChange: (i: number, patch: Partial) => void; - pinned: boolean; - onTogglePin: () => void; -} - -export function ReadoutStrip({ params, values, onChange, pinned, onTogglePin }: ReadoutStripProps) { - const [open, setOpen] = useState(null); - const timers = useRef<{ open: ReturnType | null; close: ReturnType | null }>({ - open: null, - close: null, - }); - const drag = useRef<{ i: number; moved: boolean; startY: number; el: HTMLDivElement | null; alt: boolean }>({ - i: -1, - moved: false, - startY: 0, - el: null, - alt: false, - }); - - const scheduleOpen = (i: number) => { - if (timers.current.close) clearTimeout(timers.current.close); - if (timers.current.open) clearTimeout(timers.current.open); - timers.current.open = setTimeout(() => setOpen(i), 110); - }; - const scheduleClose = () => { - if (timers.current.open) clearTimeout(timers.current.open); - if (timers.current.close) clearTimeout(timers.current.close); - timers.current.close = setTimeout(() => setOpen(null), 280); - }; - const hold = () => { - if (timers.current.close) clearTimeout(timers.current.close); - }; - - const valFromEvent = (el: HTMLDivElement, clientY: number) => { - const r = el.getBoundingClientRect(); - return Math.max(0, Math.min(1, 1 - (clientY - r.top) / r.height)); - }; - const down = (e: ReactPointerEvent, i: number) => { - e.currentTarget.setPointerCapture?.(e.pointerId); - drag.current = { - i, - moved: false, - startY: e.clientY, - el: e.currentTarget, - alt: e.altKey || e.metaKey, - }; - }; - const move = (e: ReactPointerEvent, i: number) => { - const d = drag.current; - if (d.i !== i) return; - if (Math.abs(e.clientY - d.startY) > 3) d.moved = true; - if (d.moved && !d.alt && d.el) onChange(i, { val: valFromEvent(d.el, e.clientY) }); - }; - const up = (e: ReactPointerEvent, i: number) => { - const d = drag.current; - if (d.i !== i) return; - if (d.alt && !d.moved) onChange(i, { status: RS_NEXT[params[i].status] || 'live' }); - else if (!d.moved && d.el) onChange(i, { val: valFromEvent(d.el, e.clientY) }); - drag.current = { i: -1, moved: false, startY: 0, el: null, alt: false }; - }; - - return ( -
- - {params.map((p, i) => { - const eff = values[i] ?? 0; - const gc = `var(${RS_GROUP_COLOR[p.group] || '--accent'})`; - const dim = p.status === 'off'; - const placeRight = i > params.length - 5; - return ( -
-
scheduleOpen(i)} - style={{ - textAlign: 'center', - fontSize: 8, - fontFamily: 'var(--font-mono)', - lineHeight: '11px', - cursor: 'help', - color: p.status === 'live' ? 'var(--fg-dim)' : gc, - overflow: 'hidden', - whiteSpace: 'nowrap', - textOverflow: 'ellipsis', - }} - > - {p.name} -
-
down(e, i)} - onPointerMove={(e) => move(e, i)} - onPointerUp={(e) => up(e, i)} - onPointerCancel={(e) => up(e, i)} - style={{ - position: 'relative', - flex: 1, - background: 'var(--bg)', - borderRadius: 2, - overflow: 'hidden', - cursor: 'ns-resize', - opacity: dim ? 0.5 : 1, - touchAction: 'none', - }} - > -
- {p.status === 'live' && ( -
- )} - {p.status !== 'live' && ( -
- {p.status === 'fixed' ? '⊟' : '∅'} -
- )} -
- {open === i && ( - onChange(i, patch)} - onHold={hold} - onLeave={scheduleClose} - place={{ top: 'calc(100% + 6px)', [placeRight ? 'right' : 'left']: 0 }} - /> - )} -
- ); - })} -
- ); -} diff --git a/manifold/src/console/SplitStage.tsx b/manifold/src/console/SplitStage.tsx deleted file mode 100644 index 924ad46..0000000 --- a/manifold/src/console/SplitStage.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/** - * SplitStage — input and output given EQUAL prominence, side by side. Left = - * Manifold (input), right = OutputStage (output field). Ported from `SplitStage.jsx`. - */ -import { Manifold } from './Manifold'; -import { OutputStage } from './OutputStage'; -import type { MFParam } from './model'; -import type { FeedbackMarker, Pin } from './types'; - -export interface SplitStageProps { - pos: [number, number]; - onMove: (x: number, y: number) => void; - noiseCap: number; - pins: Pin[]; - markers?: FeedbackMarker[]; - variant?: 'rectangular' | 'circular'; - follow: boolean; - onLongPress: (p: [number, number]) => void; - params: MFParam[]; - values: number[]; - onChange: (i: number, patch: Partial) => void; -} - -export function SplitStage({ - pos, - onMove, - noiseCap, - pins, - markers = [], - variant = 'rectangular', - follow, - onLongPress, - params, - values, - onChange, -}: SplitStageProps) { - return ( -
-
- -
-
- -
-
- ); -} diff --git a/manifold/src/console/VerdictCluster.tsx b/manifold/src/console/VerdictCluster.tsx index 60539ad..ebe6af8 100644 --- a/manifold/src/console/VerdictCluster.tsx +++ b/manifold/src/console/VerdictCluster.tsx @@ -1,6 +1,6 @@ /** * VerdictCluster — floating bottom-centre control, the app's main verdict. - * ▽ perturb (thumbs-down) · ↺ undo · △ commit (thumbs-up), + A/B toggle. + * ▽ perturb (thumbs-down) · ↺ undo · △ commit (thumbs-up). * Long-press perturb = full re-roll. Ported from `VerdictCluster.jsx`. * * The cluster reflects the ACTIVE feedback mode (workstream B; rl-feedback §0): @@ -50,9 +50,6 @@ export interface VerdictClusterProps { /** Full re-roll of the current net (right half of the pill). */ onRandomise: () => void; canUndo: boolean; - ab: 'A' | 'B'; - onToggleAB: () => void; - onHoldA: (holding: boolean) => void; firstSession: boolean; /** Active feedback mode — drives the cluster's labels/tones (rl-feedback §0). */ feedbackMode: FeedbackModeUI; @@ -70,9 +67,6 @@ export function VerdictCluster({ onNudge, onRandomise, canUndo, - ab, - onToggleAB, - onHoldA, firstSession, feedbackMode, exploring, diff --git a/manifold/src/console/index.ts b/manifold/src/console/index.ts index dc54fce..398bb21 100644 --- a/manifold/src/console/index.ts +++ b/manifold/src/console/index.ts @@ -2,17 +2,13 @@ * Console barrel — the convertible Console shell, wired to the real engine. */ export { ConsoleApp } from './ConsoleApp'; -export type { ConsoleAppProps } from './ConsoleApp'; export { CompositeStage } from './CompositeStage'; -export { SplitStage } from './SplitStage'; export { OutputStage } from './OutputStage'; -export { ReadoutStrip } from './ReadoutStrip'; export { Manifold } from './Manifold'; -export { InputMini } from './InputMini'; export { VerdictCluster } from './VerdictCluster'; export { Dock } from './Dock'; export { DRAWERS } from './Drawers'; -export { AltitudeNav, MiniMeters, CompactAxis } from './shared-ui'; -export { MF_MODES, shapeValues, applyCurve, seededGradient, modeEngineId } from './model'; +export { MiniMeters } from './shared-ui'; +export { MF_MODES, shapeValues, applyCurve, modeEngineId } from './model'; export type { MFMode, MFParam, ParamStatus } from './model'; -export type { Focus, ConsoleCtx } from './types'; +export type { ConsoleCtx } from './types'; diff --git a/manifold/src/console/model.ts b/manifold/src/console/model.ts index 1298214..da87cdb 100644 --- a/manifold/src/console/model.ts +++ b/manifold/src/console/model.ts @@ -69,7 +69,7 @@ export const DEFAULT_MODE_ML: ModeML = { /** * Per-output control row — the unified store used by both the stage - * (OutputStage / ReadoutStrip) and the Outputs/Routing dock. `status` is the + * (OutputStage) and the Outputs/Routing dock. `status` is the * model-control tri-state; `muted` and `armed` are ORTHOGONAL modifiers * (dock-spec §3.2 — the deliberate split of the deployed conflated * frozen↔muted field). Backend-specific specs are populated by the active @@ -302,22 +302,6 @@ export function shapeValues(params: MFParam[], engineOut: Float32Array | null): }); } -/** Deterministic per-revision gradient-flow stub (visual only; ported as-is). */ -export function seededGradient(rev: number): { - norms: number[]; - status: string[]; -} { - const n = 4; - const norms: number[] = []; - const status: string[] = []; - for (let i = 0; i < n; i++) { - const r = Math.abs((Math.sin((rev + 1) * (i + 1) * 12.9898) * 43758.5453) % 1); - norms.push(0.2 + r * 0.8); - status.push(r > 0.85 ? 'exploding' : r < 0.18 ? 'vanishing' : r < 0.3 ? 'converged' : 'healthy'); - } - return { norms, status }; -} - /** Map a mode id → the audio-engine backend id. Mode ids align with engine ids * except `slp_workshop` (runs the memlcelium engine), the analysis controller, * and the relabelled `c15`. */ diff --git a/manifold/src/console/output-mode.ts b/manifold/src/console/output-mode.ts index 06787fc..43e79b0 100644 --- a/manifold/src/console/output-mode.ts +++ b/manifold/src/console/output-mode.ts @@ -18,13 +18,6 @@ */ import type { OutputMode } from './types'; import type { BackendId } from '../dock/output-state'; -import type { - ParticleIcon, - MidiIcon, - OscIcon, - SynthIcon, - EditorIcon, -} from './icons'; export interface OutputModeDescriptor { id: OutputMode; @@ -87,11 +80,3 @@ export const DEFAULT_OUTPUT_MODE: OutputMode = OUTPUT_MODES[0].id; export function outputModeDescriptor(id: OutputMode): OutputModeDescriptor { return OUTPUT_MODES.find((m) => m.id === id) ?? OUTPUT_MODES[0]; } - -/** The monochrome icon component for a Mode (resolved by the dock). */ -export type ModeIconComponent = - | typeof ParticleIcon - | typeof MidiIcon - | typeof OscIcon - | typeof SynthIcon - | typeof EditorIcon; diff --git a/manifold/src/console/shared-ui.tsx b/manifold/src/console/shared-ui.tsx index 26c5154..d70cad8 100644 --- a/manifold/src/console/shared-ui.tsx +++ b/manifold/src/console/shared-ui.tsx @@ -1,85 +1,13 @@ /** - * Console — shared chrome for the simpler altitudes. Ported from `shared-ui.jsx`. + * Console — shared chrome. Ported from `shared-ui.jsx`. * - * AltitudeNav no longer navigates to separate HTML files (the JSX's href model); - * the focus switch is driven by React state via `onFocus`. The altitude pills - * (Console / Perform / Zen) are inert here — Manifold ships a single altitude. + * AltitudeNav and CompactAxis were deleted 2026-07 (simplification audit S15) — + * both were part of the dead focus/altitude system (setFocus was never called; + * Manifold ships a single "composite" altitude). MiniMeters survives — it is + * the live glanceable output readout used by CompositeStage's minimap. */ -import type { CSSProperties } from 'react'; -import type { Focus } from './types'; import type { MFParam } from './model'; -const FOCI: [Focus, string, string][] = [ - ['in', 'IN', 'Input-first'], - ['split', 'DUAL', 'Input + output equal'], - ['out', 'OUT', 'Output-first'], - ['composite', 'FLEX', 'Composite — drag to rebalance'], -]; - -export interface AltitudeNavProps { - current?: string; - focus?: Focus; - onFocus?: (f: Focus) => void; - style?: CSSProperties; -} - -export function AltitudeNav({ current = 'console', focus = 'in', onFocus, style }: AltitudeNavProps) { - const items = [ - { id: 'console', dots: '◆◆◆', label: 'Console' }, - { id: 'perform', dots: '◆◆', label: 'Perform' }, - { id: 'zen', dots: '◆', label: 'Zen' }, - ]; - const pill = (on: boolean): CSSProperties => ({ - textDecoration: 'none', - fontSize: 11, - padding: '2px 8px', - borderRadius: 'var(--r-pill)', - color: on ? 'var(--accent)' : 'var(--fg-dim)', - background: on ? 'rgba(255,106,0,0.14)' : 'transparent', - border: 'none', - cursor: 'pointer', - fontFamily: 'var(--font-mono)', - }); - return ( -
- {items.map((it) => ( - - {it.dots} - - ))} - - {FOCI.map(([f, label, title]) => ( - - ))} -
- ); -} - const MM_GROUP_COLOR: Record = { formant: '--accent', pitch: '--accent-2', @@ -122,66 +50,3 @@ export function MiniMeters({ params, values }: { params: MFParam[]; values: numb
); } - -/** CompactAxis — slim labelled feel slider (Perform bar; kept for parity). */ -export function CompactAxis({ - label, - value, - onChange, - accent = 'var(--accent)', -}: { - label: string; - value: number; - onChange: (v: number) => void; - accent?: string; -}) { - return ( - - ); -} diff --git a/manifold/src/console/types.ts b/manifold/src/console/types.ts index 3b742a1..c7e84e4 100644 --- a/manifold/src/console/types.ts +++ b/manifold/src/console/types.ts @@ -2,7 +2,6 @@ * Console — shared prop/context types used across the stage + dock components. */ import type { MFMode, MFParam } from './model'; -import type { BackendId } from '../dock/output-state'; import type { FeedbackMode } from '../engine/types'; import type { BackendStatus } from '../backends/backend'; import type { UseInputLayer } from '../inputs'; @@ -35,37 +34,34 @@ export interface FeedbackMarker { polarity: 'positive' | 'negative'; } -export interface Snapshot { - id: number; - tag: string; - noise: number; - seed: number; -} - export type DrawerKey = 'learn' | 'inputs' | 'route' | 'settings' | 'help'; export type DrawerDepth = 'condensed' | 'expanded'; -export type Focus = 'in' | 'split' | 'out' | 'composite'; -export interface Axes { - boldness: number; - memory: number; - precision: number; -} - -/** The flat context the Dock + drawers read. */ +/** + * The flat context the Dock + drawers read. Pruned 2026-07 (simplification + * audit S19) to the fields Dock/Drawers/OutputsBackendConfig actually consume. + * Deleted outright: the Axes type + axes/setAxis state, the + * preset/setPreset/offsetActive chain (permanently 'Sculpt'/false), the + * markers/outputBackend/setOutputBackend/cycleStatus fields (each had a zero- + * consumer duplicate elsewhere — see ConsoleApp.tsx), and everything S16/S18/ + * L1/L20 removed (decorative sliders, snapshots, health/gradient visuals, + * BackendAdvanced's catalogue). + * + * `modes` / `setModeId` are KEPT despite having no renderer today — this is + * the exact plumbing the Phase-5 instrument-mode picker is built on (A7/A3), + * not dead code. `busy` / `addingExample` / `onAddExample` / `onTrain` / `loss` + * are ALSO kept even though no drawer reads them either: unlike the fields + * above they drive real engine calls (engine.addExample / engine.train / + * engine.evalLoss), so — pending confirmation either way — they read as + * unfinished plumbing rather than confirmed-dead decoration; deleting them + * was out of this pass's authorized scope. + */ export interface ConsoleCtx { modes: MFMode[]; modeId: string; setModeId: (id: string) => void; mode: MFMode; - axes: Axes; - setAxis: (k: keyof Axes, v: number) => void; - - preset: string; - setPreset: (p: string) => void; - offsetActive: boolean; - datasetCount: number; loss: number[]; busy: boolean; @@ -74,15 +70,9 @@ export interface ConsoleCtx { onTrain: () => void; onClear: () => void; - snapshots: Snapshot[]; - onJump: (id: number) => void; - params: MFParam[]; - cycleStatus: (i: number) => void; /** Patch one output row in the shared store (drives stage + dock in sync). */ setParam: (i: number, patch: Partial) => void; - outputBackend: BackendId; - setOutputBackend: (v: BackendId) => void; // ---- Output backend transport (backends-spec §1–§5) ---- /** Live status of the active output backend (MIDI/OSC connect state, etc.). */ @@ -117,23 +107,12 @@ export interface ConsoleCtx { outputMode: OutputMode; setOutputMode: (m: OutputMode) => void; - // ---- Feedback markers on the 2D map (both polarities) ---- - /** Markers plotted at the input location where each feedback was given. */ - markers: FeedbackMarker[]; - // ---- Modular input layer (workstream F; inputs-spec) ---- /** The composed input layer: source enable/config/status + channel layout. */ inputs: UseInputLayer; - health: number; - gradient: number[]; - gradientStatus: string[]; - weightsRevision: number; - spread: boolean; setSpread: (v: boolean) => void; - tame: number; - setTame: (v: number) => void; noiseCap: number; setNoiseCap: (v: number) => void; @@ -151,14 +130,6 @@ export interface ConsoleCtx { /** Clear all arm flags ("Arm all"). */ clearArmed: () => void; - // ---- Live training params (dock-spec §1.3) ---- - learningRate: number; - setLearningRate: (v: number) => void; - decay: number; - setDecay: (v: number) => void; - spreadLevel: number; - setSpreadLevel: (v: number) => void; - // ---- Exploration gestures (one-core-engine §P1; interim TS shells) ---- /** True while the Jolt press-and-hold weight-morph is engaged. */ joltActive: boolean; @@ -173,10 +144,6 @@ export interface ConsoleCtx { // ---- Synth engine (dock-spec §5) ---- audioStarted: boolean; onToggleAudio: () => void; - volume: number; - setVolume: (v: number) => void; - bpm: number; - setBpm: (v: number) => void; // ---- Explore-and-place scratchpad session (workstream B; rl-feedback §2.2) ---- /** True while awaiting a manifold location pick after pressing "place". */ diff --git a/manifold/src/dock/BackendAdvanced.tsx b/manifold/src/dock/BackendAdvanced.tsx deleted file mode 100644 index 575686c..0000000 --- a/manifold/src/dock/BackendAdvanced.tsx +++ /dev/null @@ -1,354 +0,0 @@ -/** - * BackendAdvanced — the FULL-depth advanced backend modal bodies (dock-spec §4). - * One editor per backend. All backends share the §3.1 baseline (rendered as - * OutputControlRow elsewhere); these add the backend-specific fields. - * - * The backend transport (backends-spec workstream E) is now LIVE: editing these - * fields writes the shared MFParam store, which the BackendManager reads to send - * real Web MIDI CC / OSC-over-WS. This modal is the full-depth duplicate of the - * inline config in OutputsBackendConfig; both write the same store. - */ -import type { MFParam } from '../console/model'; -import type { BackendId, CvChannelId } from './output-state'; -import { CV_CHANNELS, defaultCvSpec, defaultMidiSpec, defaultOscSpec } from './output-state'; - -function num(s: string, fallback: number): number { - const v = parseFloat(s); - return Number.isFinite(v) ? v : fallback; -} - -const cellInput: React.CSSProperties = { - width: '100%', - background: 'var(--bg-1)', - border: '1px solid var(--line)', - borderRadius: 'var(--r-1)', - color: 'var(--fg)', - fontFamily: 'var(--font-mono)', - fontSize: 'var(--fs-xs)', - padding: '3px 6px', -}; - -function Th({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ); -} - -export interface BackendAdvancedProps { - backend: BackendId; - params: MFParam[]; - setParam: (i: number, patch: Partial) => void; -} - -export function BackendAdvanced({ backend, params, setParam }: BackendAdvancedProps) { - switch (backend) { - case 'midi': - return ; - case 'osc': - return ; - case 'vcv': - return ; - case 'cvgate': - return ; - default: - return ; - } -} - -// ---- MIDI (dock-spec §4.1) ------------------------------------------------- - -function MidiCcEditor({ - params, - setParam, -}: { - params: MFParam[]; - setParam: (i: number, patch: Partial) => void; -}) { - return ( -
-

- {params.length} CCs · live Web MIDI out (backends-spec §2.3). Editing the CC map here sends in - real time once a MIDI port is selected in the Outputs panel. -

-
- - - - - - - - - - - {params.map((p, i) => { - const m = p.midi ?? defaultMidiSpec(i); - return ( - - - - - - - ); - })} - -
NameCC#ChState
- setParam(i, { midi: { ...m, name: e.target.value } })} - /> - - - setParam(i, { - midi: { ...m, cc: Math.max(0, Math.min(127, num(e.target.value, m.cc))) }, - }) - } - /> - - - setParam(i, { - midi: { - ...m, - channel: Math.max(1, Math.min(16, num(e.target.value, m.channel))), - }, - }) - } - /> - - {p.status} - {p.muted ? ' · muted' : ''} -
-
-
- ); -} - -// ---- OSC (dock-spec §4.2) -------------------------------------------------- - -function OscPathEditor({ - params, - setParam, -}: { - params: MFParam[]; - setParam: (i: number, patch: Partial) => void; -}) { - return ( -
-

- Live OSC over the WebSocket bridge (backends-spec §2.4). Set the bridge URL + per-output paths in - the Outputs panel; emits only while the bridge process is connected. -

-
- - - - - - - - - - {params.map((p, i) => { - const o = p.osc ?? defaultOscSpec(p.name); - return ( - - - - - - ); - })} - -
OSC pathrange minrange max
- setParam(i, { osc: { ...o, path: e.target.value } })} - /> - - setParam(i, { osc: { ...o, rangeMin: num(e.target.value, o.rangeMin) } })} - /> - - setParam(i, { osc: { ...o, rangeMax: num(e.target.value, o.rangeMax) } })} - /> -
-
-
- ); -} - -// ---- VCV / CV (dock-spec §4.3) --------------------------------------------- - -function VcvChannelEditor({ - params, - setParam, -}: { - params: MFParam[]; - setParam: (i: number, patch: Partial) => void; -}) { - return ( -
-

- VCV adds nothing beyond the baseline (min/max = range, fixed = freeze) plus per-channel polarity. - {/* TODO(backends-spec §2.6): the VCV browser↔module bridge transport is not yet wired here. */} -

-
- {params.map((p, i) => { - const bipolar = p.vcv?.bipolar ?? false; - return ( -
- {p.name} - - {p.min.toFixed(2)}–{p.max.toFixed(2)} · {p.status === 'fixed' ? 'frozen' : 'live'} - - -
- ); - })} -
-
- ); -} - -// ---- uSEQ CV / gate (docs/specs/useq-cv-protocol.md) ------------------------------------- - -function CvChannelEditor({ - params, - setParam, -}: { - params: MFParam[]; - setParam: (i: number, patch: Partial) => void; -}) { - return ( -
-

- Live CV/gate over USB serial to a uSEQ module + expander (docs/specs/useq-cv-protocol.md). Assign each model output to a - CV jack or gate; gates threshold the mapped 0–1 value. Connect the device in the Outputs panel. -

-
- - - - - - - - - - {params.map((p, i) => { - const c = (p.cv as { channel: CvChannelId; gateThreshold: number } | undefined) ?? defaultCvSpec(i); - const isGate = c.channel.startsWith('gate'); - return ( - - - - - - ); - })} - -
OutputuSEQ channelGate ≥
{p.name} - - - - setParam(i, { - cv: { ...c, gateThreshold: Math.max(0, Math.min(1, num(e.target.value, c.gateThreshold))) }, - }) - } - /> -
-
-
- ); -} - -function SynthGroupNote({ params }: { params: MFParam[] }) { - const groups = Array.from(new Set(params.map((p) => p.group))); - return ( -
-

- The synth backend's advanced surface is the group-override matrix — see the Powerful Synth Engine - drawer's full depth (dock-spec §4.4 / §5). Groups: {groups.join(' · ')}. -

-
- ); -} diff --git a/manifold/src/dock/OutputsBackendConfig.tsx b/manifold/src/dock/OutputsBackendConfig.tsx index 62e503b..699cf06 100644 --- a/manifold/src/dock/OutputsBackendConfig.tsx +++ b/manifold/src/dock/OutputsBackendConfig.tsx @@ -12,8 +12,10 @@ * - Synth/Particle/Editor → handled by ModeConfig in Drawers (no extra config here). * * Everything is editable inline; writes go through the shared MFParam store - * (ctx.setParam) — never a second data path. The full-depth modal reuses the - * same sections via BackendAdvanced. + * (ctx.setParam) — never a second data path. This is the ONLY per-backend + * config editor (BackendAdvanced.tsx, a full-depth duplicate that rendered + * alongside this one in the same expanded drawer, was deleted 2026-07 — + * simplification audit S18). */ import { useEffect, useState } from 'react'; import type { ConsoleCtx } from '../console/types'; diff --git a/manifold/src/dock/output-state.ts b/manifold/src/dock/output-state.ts index 7800519..32e3e84 100644 --- a/manifold/src/dock/output-state.ts +++ b/manifold/src/dock/output-state.ts @@ -14,8 +14,8 @@ * They compose freely (e.g. an output can be `off` AND `muted` AND `armed`). * Recorded in ALIGNMENT.md. * - * To keep the dock tri-state and the existing OutputStage / ReadoutStrip - * tri-state in sync WITHOUT a second data path, this model is folded onto the + * To keep the dock tri-state and the existing OutputStage tri-state in sync + * WITHOUT a second data path, this model is folded onto the * existing `MFParam` (model.ts) — `MFParam.status` carries `state`, and the new * `muted` / `armed` / backend fields live alongside it. ConsoleApp owns the * single `MFParam[]` store; the dock and the stage both read/write it. @@ -29,23 +29,6 @@ export type OutputState = ParamStatus; // 'off' | 'fixed' | 'live' /** The selectable output backend (dock-spec §3.4; backends-spec §1). */ export type BackendId = 'synth' | 'particles' | 'midi' | 'osc' | 'cvgate' | 'vcv'; -export interface BackendDescriptor { - id: BackendId; - /** Dock label — NEVER "C15" (backends-spec naming guard). */ - label: string; - description: string; -} - -/** The backend roster surfaced in the dock's backend selector. */ -export const BACKENDS: readonly BackendDescriptor[] = [ - { id: 'synth', label: 'Powerful Synth Engine', description: 'Firmware-parity built-in audio engine.' }, - { id: 'midi', label: 'MIDI', description: 'Web MIDI CC out — per-output CC#/channel.' }, - { id: 'osc', label: 'OSC', description: 'OSC bridge — named paths + physical ranges.' }, - { id: 'cvgate', label: 'CV', description: 'uSEQ CV/gate over USB serial — 11 CV + 3 gate.' }, - { id: 'vcv', label: 'VCV', description: 'VCV Rack module — 16 CV outs with LED rings.' }, - { id: 'particles', label: 'Particle', description: 'Flow-field visualiser (no audio).' }, -] as const; - // ---- Backend-specific per-output specs (dock-spec §4) ---------------------- /** MIDI CC backend per-output extras (dock-spec §4.1). */ @@ -111,47 +94,6 @@ export interface CvSpec { gateThreshold: number; } -/** - * The full per-output control. This is the spec's `OutputControl` (dock-spec - * §3.2). It is represented on `MFParam` for the shared store; this interface - * documents the complete contract and is what {@link toOutputControl} yields. - */ -export interface OutputControl { - index: number; - name: string; - group: string; - state: OutputState; // off | fixed | live - muted: boolean; // downstream silence; still computed - armed: boolean; // solo / focus-training (=arm) - min: number; // [0,1] - max: number; // [0,1], min<=max - curve: number; // [0,1], 0.5 linear - fixedValue: number; // held value when state==='fixed' - // backend-specific, populated by the active backend adapter: - midi?: MidiCcSpec; - osc?: OscSpec; - vcv?: VcvSpec; -} - -/** Project an MFParam (the shared store row) into the full OutputControl view. */ -export function toOutputControl(p: MFParam, index: number): OutputControl { - return { - index, - name: p.name, - group: p.group, - state: p.status, - muted: p.muted ?? false, - armed: p.armed ?? false, - min: p.min, - max: p.max, - curve: p.curve, - fixedValue: p.val, - midi: p.midi, - osc: p.osc, - vcv: p.vcv, - }; -} - /** * Build the focus / solo mask from the per-row armed flags (dock-spec §1.2). * Returns null when nothing is armed (⇒ all outputs active / no focus). diff --git a/manifold/src/feedback/controller.ts b/manifold/src/feedback/controller.ts index f1a39d2..68412e1 100644 --- a/manifold/src/feedback/controller.ts +++ b/manifold/src/feedback/controller.ts @@ -15,7 +15,7 @@ * process() — re-run last input after a weight change * addExample([x,y], outVec) — append a training example * train() — SGD over the dataset - * feedback.{setFocus,thumbsUp,dislikeGeometric,storePositive,…} + * feedback.{setFocus,thumbsUp,dislikeGeometric,…} * — the SHARED C++ core's RL primitives * * As of one-core-engine P3 the geometric push-away (Mode 1) is a C++ core @@ -55,9 +55,6 @@ export interface Anchor { /** The minimal engine surface the controller needs (decoupled from EngineApi). */ export interface ControllerEngine { - getWeights(): Float32Array; - setWeights(w: Float32Array): void; - randomise(spread?: number): void; setInput(x: number, y: number): void; getOutputs(): Float32Array; process(): void; @@ -70,8 +67,6 @@ export interface ControllerEngine { // `heardVec` is the post-pipeline (HEARD) output; returns the FeedbackAction // int (14=GeometricPush, 15=GeometricColdStart). dislikeGeometric(heardVec?: Float32Array, lr?: number): number; - /** Feed a positive into the k-NN centroid (null → live MLP output). */ - storePositive(vec?: Float32Array): void; positiveCount(): number; negativeCount(): number; // ExploreAndPlace lifecycle — the SHARED C++ core (mode 'explore_and_place'). @@ -87,8 +82,6 @@ export interface ControllerEngine { like(): void; commitPlace(): void; cancelPlace(): void; - placing(): boolean; - exploreState(): number; // 0=Idle 1=Exploring 2=Placing undoDepth(): number; placedOutput(): Float32Array | null; }; @@ -115,24 +108,16 @@ export interface FeedbackControllerState { } export interface FeedbackControllerOptions { - /** Seed for the deterministic nudge RNG (NOT Math.random — task constraint). */ - seed?: number; /** Master spread for randomise / nudge (mirrors the engine spread knob). */ spread?: number; /** Nudge perturbation standard deviation (small bounded weight jitter). */ nudgeStddev?: number; - /** - * Undo-stack depth. WASM D=4, firmware D=2 per rl-feedback-design §2.2; the - * prototype defaults to the WASM depth. - */ - undoDepth?: number; } export class FeedbackController { private engine: ControllerEngine; private spread: number; private nudgeStddev: number; - private maxUndo: number; private mode: ProtoFeedbackMode = 'explore-and-place'; private soloMode: ProtoSoloMode = 'mask-gradients'; @@ -162,7 +147,6 @@ export class FeedbackController { this.engine = engine; this.spread = opts.spread ?? 0.6; this.nudgeStddev = opts.nudgeStddev ?? 0.05; - this.maxUndo = Math.max(1, opts.undoDepth ?? 4); } // =================================================================== @@ -367,23 +351,12 @@ export class FeedbackController { * 5. cold-start fallback (negative-LR) when there are no positives yet. * Soloed/active dims come from the core's focus mask (set via setArmMask). * - * @param input the control input the disliked sound was heard at (unused by - * the core — it reads the MLP's live input — kept for the marker / - * call-site symmetry with like()). * @param output the HEARD (post-pipeline) output vector a_neg. MUST be the * heard vector, not the raw MLP output, or the cold-start MSE * derivative is zero (see engine.feedback.dislikeGeometric). - * @param _speed legacy move_weights speed — ignored (geometric path). - * @param _spread legacy move_weights spread — ignored (geometric path). * @returns the FeedbackAction int (14=GeometricPush, 15=GeometricColdStart). */ - dislike( - input: readonly [number, number], - output: Float32Array, - _speed: number, - _spread: number, - ): number { - void input; + dislike(output: Float32Array): number { const action = this.engine.feedback.dislikeGeometric(output); this.engine.process(); return action; diff --git a/manifold/src/primitives/ControlAxis.tsx b/manifold/src/primitives/ControlAxis.tsx deleted file mode 100644 index cdebcf5..0000000 --- a/manifold/src/primitives/ControlAxis.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import type { CSSProperties, ReactNode } from 'react'; - -export interface ControlAxisProps { - label?: ReactNode; - /** Bipolar endpoint labels, e.g. ['Caution', 'Bold']. */ - endpoints?: [ReactNode, ReactNode]; - value?: number; - onChange?: (value: number) => void; - /** Live preset tag shown next to the label. */ - preset?: ReactNode; - /** Per-axis track/thumb accent colour (any CSS colour or var()). */ - accent?: string; - disabled?: boolean; - style?: CSSProperties; -} - -/** - * Manifold ControlAxis — a named macro slider with bipolar endpoint labels - * (e.g. Boldness: Caution ↔ Bold). Shows a live preset tag and value. The - * track accent can be themed per-axis via `accent`. - * - * Relies on the `.mf-axis-input` rules in `styles/primitives.css`; the accent - * is passed via the inline `--mf-axis-accent` custom property. - */ -export function ControlAxis({ - label, - endpoints = ['', ''], - value = 0.5, - onChange, - preset, - accent = 'var(--accent)', - disabled = false, - style, -}: ControlAxisProps) { - return ( -
-
- - {label} - - {preset && ( - - {preset} - - )} - - {value.toFixed(2)} - -
- onChange?.(parseFloat(e.target.value))} - className="mf-axis-input" - style={ - { - WebkitAppearance: 'none', - appearance: 'none', - width: '100%', - height: 24, - background: 'transparent', - margin: 0, - cursor: 'pointer', - '--mf-axis-accent': accent, - } as CSSProperties - } - /> -
- {endpoints[0]} - {endpoints[1]} -
-
- ); -} diff --git a/manifold/src/primitives/CurvePlot.tsx b/manifold/src/primitives/CurvePlot.tsx deleted file mode 100644 index 251567a..0000000 --- a/manifold/src/primitives/CurvePlot.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { useEffect, useRef } from 'react'; -import type { CSSProperties } from 'react'; - -export type CurveName = - | 'linear' - | 'exp' - | 'log' - | 'square' - | 'sqrt' - | 'sigmoid' - | 'cubic' - | 'centered_power'; - -export interface CurvePlotProps { - /** One of the named response curves. Ignored when `fn` is provided. */ - curve?: CurveName; - /** Custom response function f:[0,1]→[0,1]. Overrides `curve`. */ - fn?: (x: number) => number; - width?: number; - height?: number; - /** Stroke colour (any CSS colour or var()). */ - color?: string; - showAxes?: boolean; - ariaLabel?: string; - style?: CSSProperties; -} - -const clamp01 = (v: number): number => (v < 0 ? 0 : v > 1 ? 1 : v); - -const CURVES: Record number> = { - linear: (x) => x, - exp: (x) => (Math.exp(4 * x) - 1) / (Math.exp(4) - 1), - log: (x) => Math.log(1 + x * (Math.exp(4) - 1)) / 4, - square: (x) => x * x, - sqrt: (x) => Math.sqrt(clamp01(x)), - sigmoid: (x) => { - const s = (v: number) => 1 / (1 + Math.exp(-(v - 0.5) * 8)); - const lo = s(0); - const hi = s(1); - return (s(x) - lo) / (hi - lo); - }, - cubic: (x) => { - const v = clamp01(x); - return v * v * (3 - 2 * v); - }, - centered_power: (x) => { - const o = x - 0.5; - const sg = o < 0 ? -1 : 1; - return clamp01((sg * Math.pow(Math.abs(o) * 2, 0.5)) / 2 + 0.5); - }, -}; - -/** - * Manifold CurvePlot — renders one of the named response curves (or a custom - * function f:[0,1]→[0,1]) on the dark grid. The brand's straight-line & - * parabolic/bézier motif. - */ -export function CurvePlot({ - curve = 'cubic', - fn, - width = 200, - height = 120, - color = 'var(--accent)', - showAxes = true, - ariaLabel, - style, -}: CurvePlotProps) { - const ref = useRef(null); - - useEffect(() => { - const cv = ref.current; - if (!cv) return; - const dpr = window.devicePixelRatio || 1; - const w = width * dpr; - const h = height * dpr; - cv.width = w; - cv.height = h; - const ctx = cv.getContext('2d'); - if (!ctx) return; - ctx.clearRect(0, 0, w, h); - const cs = getComputedStyle(cv); - const stroke = color.startsWith('var(') - ? cs.getPropertyValue(color.slice(4, -1).trim()).trim() || '#ff6a00' - : color; - const pad = 6 * dpr; - - if (showAxes) { - ctx.strokeStyle = 'rgba(255,255,255,0.06)'; - ctx.lineWidth = 1; - ctx.strokeRect(0.5, 0.5, w - 1, h - 1); - ctx.beginPath(); - ctx.moveTo(0, h / 2); - ctx.lineTo(w, h / 2); - ctx.stroke(); - ctx.beginPath(); - ctx.moveTo(w / 2, 0); - ctx.lineTo(w / 2, h); - ctx.stroke(); - } - const f = fn || CURVES[curve] || CURVES.linear; - ctx.strokeStyle = stroke; - ctx.lineWidth = 2 * dpr; - ctx.beginPath(); - for (let p = 0; p <= 120; p++) { - const x = p / 120; - const y = clamp01(f(x)); - const px = pad + x * (w - 2 * pad); - const py = h - pad - y * (h - 2 * pad); - if (p === 0) ctx.moveTo(px, py); - else ctx.lineTo(px, py); - } - ctx.stroke(); - }, [curve, fn, width, height, color, showAxes]); - - return ( - - ); -} diff --git a/manifold/src/primitives/Panel.tsx b/manifold/src/primitives/Panel.tsx deleted file mode 100644 index 0ed88b7..0000000 --- a/manifold/src/primitives/Panel.tsx +++ /dev/null @@ -1,82 +0,0 @@ -import type { CSSProperties, ReactNode } from 'react'; - -export interface PanelProps { - title?: ReactNode; - /** Small uppercase eyebrow shown before the title. */ - label?: ReactNode; - /** Right-aligned header actions. */ - actions?: ReactNode; - children?: ReactNode; - padding?: string; - style?: CSSProperties; -} - -/** - * Manifold Panel — the house surface: bg-1 fill, 1px hairline border, 8px - * radius, no shadow. Optional header row with an uppercase title + actions, - * separated by a hairline. - */ -export function Panel({ - title, - label, - actions, - children, - padding = 'var(--sp-3)', - style, -}: PanelProps) { - return ( -
- {(title || label || actions) && ( -
- {label && ( - - {label} - - )} - {title && ( -

- {title} -

- )} - {actions && ( -
- {actions} -
- )} -
- )} -
{children}
-
- ); -} diff --git a/manifold/src/primitives/Sparkline.tsx b/manifold/src/primitives/Sparkline.tsx deleted file mode 100644 index 5db5b8c..0000000 --- a/manifold/src/primitives/Sparkline.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { useEffect, useRef } from 'react'; -import type { CSSProperties } from 'react'; - -export interface SparklineProps { - data?: number[]; - width?: number; - height?: number; - /** Stroke colour (any CSS colour or var()). */ - color?: string; - /** Plot on a log scale (log(max(1e-10, v) + 1)). */ - log?: boolean; - /** Render the last-value readout in the top-right. */ - showLast?: boolean; - /** Custom formatter for the last-value readout. */ - format?: (value: number) => string; - ariaLabel?: string; - style?: CSSProperties; -} - -/** - * Manifold Sparkline — a compact time-series trace (training loss, a feature - * envelope). Cyan line on a faint grid, with an optional last-value readout. - */ -export function Sparkline({ - data = [], - width = 320, - height = 70, - color = 'var(--accent-2)', - log = false, - showLast = true, - format, - ariaLabel = 'time series', - style, -}: SparklineProps) { - const ref = useRef(null); - - useEffect(() => { - const cv = ref.current; - if (!cv) return; - const dpr = window.devicePixelRatio || 1; - const w = width * dpr; - const h = height * dpr; - cv.width = w; - cv.height = h; - const ctx = cv.getContext('2d'); - if (!ctx) return; - ctx.clearRect(0, 0, w, h); - if (!data.length) return; - - const cs = getComputedStyle(cv); - const stroke = color.startsWith('var(') - ? cs.getPropertyValue(color.slice(4, -1).trim()).trim() || '#00ccff' - : color; - - const ys = data.map((v) => (log ? Math.log(Math.max(1e-10, v) + 1) : v)); - let lo = Infinity; - let hi = -Infinity; - for (const y of ys) { - if (y < lo) lo = y; - if (y > hi) hi = y; - } - if (hi === lo) hi = lo + 1e-6; - - ctx.strokeStyle = 'rgba(255,255,255,0.05)'; - ctx.lineWidth = 1; - for (let i = 1; i < 4; i++) { - const y = (i / 4) * h; - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(w, y); - ctx.stroke(); - } - - ctx.strokeStyle = stroke; - ctx.lineWidth = 1.5 * dpr; - ctx.beginPath(); - for (let i = 0; i < ys.length; i++) { - const x = (i / Math.max(1, ys.length - 1)) * w; - const norm = (ys[i] - lo) / (hi - lo); - const y = h - norm * h; - if (i === 0) ctx.moveTo(x, y); - else ctx.lineTo(x, y); - } - ctx.stroke(); - - if (showLast) { - const last = data[data.length - 1]; - const txt = format - ? format(last) - : typeof last === 'number' - ? last.toExponential(2) - : String(last); - ctx.fillStyle = '#9a9a9a'; - ctx.font = `${10 * dpr}px ui-monospace, monospace`; - ctx.textAlign = 'right'; - ctx.fillText(txt, w - 4 * dpr, 12 * dpr); - } - }, [data, width, height, color, log, showLast, format]); - - return ( - - ); -} diff --git a/manifold/src/primitives/StatusLine.tsx b/manifold/src/primitives/StatusLine.tsx deleted file mode 100644 index fee9f5e..0000000 --- a/manifold/src/primitives/StatusLine.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Fragment } from 'react'; -import type { CSSProperties, ReactNode } from 'react'; - -export type StatusTone = 'accent' | 'cyan' | 'good' | 'warn' | 'bad'; - -export interface StatusItemObject { - label?: ReactNode; - value: ReactNode; - tone?: StatusTone; -} - -export type StatusItem = string | StatusItemObject; - -export interface StatusLineProps { - items?: StatusItem[]; - style?: CSSProperties; -} - -const TONE_COLORS: Record = { - accent: 'var(--accent)', - cyan: 'var(--accent-2)', - good: 'var(--good)', - warn: 'var(--warn)', - bad: 'var(--bad)', -}; - -/** - * Manifold StatusLine — the dim mono readout strip at the bottom of a mode. - * Pass an array of items; strings render plain, {label,value,tone} render a - * labelled readout. Items are joined with the house middle-dot separator. - */ -export function StatusLine({ items = [], style }: StatusLineProps) { - return ( -

- {items.map((it, i) => { - const isObj = it !== null && typeof it === 'object'; - const toneColor = - isObj && it.tone ? (TONE_COLORS[it.tone] ?? null) : null; - return ( - - {i > 0 && } - {isObj ? ( - - {it.label && {it.label} } - - {it.value} - - - ) : ( - {it} - )} - - ); - })} -

- ); -} diff --git a/manifold/src/primitives/index.ts b/manifold/src/primitives/index.ts index 939f6be..593ea23 100644 --- a/manifold/src/primitives/index.ts +++ b/manifold/src/primitives/index.ts @@ -3,9 +3,13 @@ * on the Manifold design tokens. Ported from the window-global JSX reference * implementations in docs/redesign/manifold-export/components/. * - * Side-effect import: pulls in the `.mf-slider-input` / `.mf-axis-input` - * range-input styling that Slider and ControlAxis depend on. Importing this - * barrel anywhere in the app is enough to register those rules. + * Side-effect import: pulls in the `.mf-slider-input` range-input styling that + * Slider depends on. Importing this barrel anywhere in the app is enough to + * register those rules. + * + * Panel / StatusLine / ControlAxis / CurvePlot / Sparkline were deleted + * 2026-07 (simplification audit L22) — zero consumers repo-wide. Sparkline/ + * CurvePlot may return with the deferred training-health diagnostics suite. */ import '../styles/primitives.css'; @@ -18,34 +22,14 @@ export type { SliderProps } from './Slider'; export { PillToggle } from './PillToggle'; export type { PillToggleProps, PillOption } from './PillToggle'; -export { Panel } from './Panel'; -export type { PanelProps } from './Panel'; - export { Badge } from './Badge'; export type { BadgeProps, BadgeTone } from './Badge'; export { Switch } from './Switch'; export type { SwitchProps } from './Switch'; -export { StatusLine } from './StatusLine'; -export type { - StatusLineProps, - StatusItem, - StatusItemObject, - StatusTone, -} from './StatusLine'; - export { XYPad } from './XYPad'; export type { XYPadProps } from './XYPad'; export { VirtualJoystick } from './VirtualJoystick'; export type { VirtualJoystickProps } from './VirtualJoystick'; - -export { ControlAxis } from './ControlAxis'; -export type { ControlAxisProps } from './ControlAxis'; - -export { CurvePlot } from './CurvePlot'; -export type { CurvePlotProps, CurveName } from './CurvePlot'; - -export { Sparkline } from './Sparkline'; -export type { SparklineProps } from './Sparkline'; diff --git a/manifold/src/styles/primitives.css b/manifold/src/styles/primitives.css index 3c3e5bb..3cefc57 100644 --- a/manifold/src/styles/primitives.css +++ b/manifold/src/styles/primitives.css @@ -70,37 +70,3 @@ .mf-slider-input:focus::-webkit-slider-thumb { box-shadow: 0 0 0 3px var(--glow-focus); } - -/* ---- ControlAxis (.mf-axis-input) ---- */ -.mf-axis-input::-webkit-slider-runnable-track { - height: 6px; - border-radius: 999px; - background: var(--bg-3); -} -.mf-axis-input::-moz-range-track { - height: 6px; - border-radius: 999px; - background: var(--bg-3); -} -.mf-axis-input::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 18px; - height: 18px; - border-radius: 50%; - background: var(--mf-axis-accent, var(--accent)); - margin-top: -6px; - box-shadow: 0 0 10px var(--mf-axis-accent, var(--accent)); - cursor: pointer; -} -.mf-axis-input::-moz-range-thumb { - width: 18px; - height: 18px; - border-radius: 50%; - background: var(--mf-axis-accent, var(--accent)); - border: none; - box-shadow: 0 0 10px var(--mf-axis-accent, var(--accent)); -} -.mf-axis-input:focus { - outline: none; -}