mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 03:09:57 +00:00
feat(onboarding): input-device setup step + core-owned midi-input domain (#526)
* feat(capabilities): add core-owned midi-input control-plane domain (#873, #880) The MIDI analog of audio-input: a core-owned provider-coordinator over MIDI device discovery, selection, and shared open/close sessions. Separate from audio-input (whose source/open contract is audio-frame-centric) and not owned by any feature plugin, so the device-access boundary outlives the input-setup wizard. `discover` is the Web-MIDI permission boundary; selection persists by redaction-safe logicalSourceKey; diagnostics redact device labels and never carry raw MIDI messages. - static/capabilities/midi-input.js + load-order wiring in both shells - spec 012 + capability-domains/safety-matrix entries; midi-control narrowed to mappings-only (split) - 9 domain tests against the real runtime Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(input_setup): bundled plugin owning input-calibration + Web-MIDI provider (#872) Bundled core plugin that supplies the Web-MIDI source provider to the core midi-input domain, owns the input-calibration workflow domain (run/status/ inspect), and renders the per-instrument wizard (guitar/bass -> audio-input + note_detect; keys/drums -> midi-input live note/pad test). Idempotent hydration; redaction-safe. .gitignore allowlists the in-tree plugin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(onboarding): input-device setup step between paths and calibration (#874) After instrument-path selection and before the note-detect calibration challenge, dispatch input-calibration `run` (fire-and-launch) and await the `calibration-done` event. Fail-soft: a non-handled outcome (plugin/runtime absent) advances immediately so onboarding can never be stranded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(midi-input): ship a built-in Web-MIDI provider in the core domain Move the Web-MIDI source provider out of input_setup and into the core midi-input domain so every consumer (piano, drums, input_setup) gets MIDI devices from the domain without depending on any one plugin being loaded. input_setup is now a pure midi-input requester (manifest role updated). Prepares piano/drums full consumption (#876/#877). +1 domain test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(input_setup): Settings panel to re-run input setup (#878) Adds a settings.html with a "Set up input devices" button (window ._inputSetupRelaunch) that re-runs the wizard for the player's selected instrument paths (from /api/progression; falls back to all instruments). Makes the calibration wizard re-launchable outside first-run onboarding. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(midi-control): formalize the midi-input/midi-control split (#882) Narrow the reserved midi-control domain to mappings ONLY (CC/pitchbend/note → action routing), consuming the delivered midi-input domain for device access. Adds spec 013 defining the contract + intended consumers (feedback-plugin-midi, drums learn-mode), updates the safety-matrix row, and cross-references it from capability-domains. Per governance, midi-control stays RESERVED (no runtime domain) until a concrete mapping consumer + tests exist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(onboarding): wait for input_setup before the calibration step (#874) The input-setup wizard is a mandatory onboarding step, but plugins load asynchronously — in the desktop app (40+ plugins) the user can reach path selection and click Next before input_setup has registered its input-calibration owner. The dispatch then got a no-owner outcome and onboarding fell through to the calibration challenge, silently skipping the wizard. Now wait (bounded, 8s) for the plugin's public global before dispatching; fall through only if it never appears. Race-verified. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(onboarding): add Song directory step after name+avatar (#874) New first-run step (now step 2 of 4: name+avatar → song directory → paths → calibration challenge) where the player sets their songs folder, fixing the "folder not configured" error on a fresh install. Saves to settings (dlc_dir) and kicks a library scan; persists to config.json so it survives restart. A native folder picker is offered on desktop (window.slopsmithDesktop .pickDirectory); web users type/paste the path. "Skip for now" leaves it unconfigured (settable later in Settings). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(input_setup): filter MIDI entries out of the guitar audio-input picker (#876) Other plugins export pseudonymized MIDI sources ('midi-input-N') into the audio-input domain; they aren't audio inputs and the cryptic labels confused the guitar/bass device dropdown. Filter them out so only real audio inputs show. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(input_setup): de-dupe audio input picker entries (#876) The desktop audio engine enumerates the same device under multiple driver types, so the guitar audio-input dropdown showed repeated entries. De-dupe by display label (paired with the desktop fix that surfaces real device names). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi-input): drop vanished devices on re-discovery; reset setup confirm on switch Codex preflight findings: - midi-input domain `_discover()` only upserted enumerated sources, so an unplugged device (statechange re-discovery) lingered in list-sources and later open/select hit stale state. Reconcile each provider's sources against the fresh enumeration (close any live session, keep the selectedKey preference). - input_setup MIDI panel left "Continue" enabled (and the instrument marked done) after switching the device selection following a prior hit. Reset the waiting state + disable Continue on every selection change, and discard a stale open if the selection changed mid-await. +1 reconciliation test. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi-input): coalesce concurrent opens; commit shown audio source pre-calibration Codex re-review (round 2): - midi-input domain: two concurrent open-source calls for the same source both passed the `sessions.get` guard and each called provider.open(), which for the built-in Web-MIDI provider overwrites the shared input.onmidimessage handler and orphans the earlier session — leaving the device silent. Coalesce in-flight opens onto one provider session (await the pending open, adopt its session; re-check after open and release a redundant handle if another open won). +test. - input_setup: the guitar/bass audio <select> shows its first option by default but fires no `change`, so on a first run with nothing selected, audio-input was never told before launchCalibration(). Commit the shown option on render. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(midi-input): longer timeout for MIDI permission commands; stale-open guard in wizard Codex re-review (round 3): - The advertised command surface ran `discover`/`open-source` through the 250 ms default handler timeout, but those front a real Web-MIDI permission prompt / device open that commonly takes longer, so dispatch returned `failed` while the operation was still completing. Add per-(capability,command) timeout overrides (15 s for those two), folding the existing audio-mix special-case into the same table so both the command() and dispatch() paths honor it. - input_setup MIDI panel: openSelected() compared the mutable shared `activeKey` after its awaits, so a device switch mid-open could bind the old device's listener / close the wrong session. Capture the requested key in a local and use a generation guard to discard a superseded open. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(onboarding): detect 200-with-error song-dir saves; close MIDI session on skip Codex re-review (round 4): - /api/settings reports an invalid folder as a 200 response with an `error` body (a bare dict return, not a non-2xx status), so saveSongDir's res.ok-only check treated the failure as success and advanced onboarding without saving. Parse the body and throw on `error` too. - input_setup: the opened MIDI test session was only closed on the Continue button, so using the generic "Skip for now" after scanning leaked the listener and kept the Web-MIDI input live. Run teardown on every panel exit via a per-panel cleanup hook invoked by advance(). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(input_setup): don't hard-code Web MIDI in the device wizard Codex re-review (round 5): the MIDI panel gated availability on navigator.requestMIDIAccess and filtered sources to providerId === 'web-midi', which defeats the midi-input domain's provider-coordinator abstraction — a native/desktop MIDI adapter registered with the domain would be reported unavailable and hidden from the picker. Gate availability on the domain (window.slopsmith.midiInput) and show every source it surfaces. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
313348a1ff
commit
fb06e288e1
@@ -0,0 +1,110 @@
|
||||
# Spec 012 — MIDI-Input Control-Plane Capability Domain
|
||||
|
||||
**Status:** active (control-plane slice) · **Issues:** #873 (impl), #880 (this spec) · **Base:** `release/v0.3.0`
|
||||
|
||||
## Summary
|
||||
|
||||
`midi-input` is a **core-owned provider-coordinator** capability domain for MIDI
|
||||
device discovery, selection, and open/close session lifecycle — the MIDI analog
|
||||
of `audio-input` (spec 006). It gives every MIDI consumer in Slopsmith (the
|
||||
`input_setup` onboarding wizard, the `piano`/keys and `drums` plugins, and — as
|
||||
a follow-up — note-detection's Web-MIDI provider) **one device-access boundary**:
|
||||
one permission prompt, one source list, one redaction boundary.
|
||||
|
||||
## Motivation
|
||||
|
||||
Today each MIDI consumer calls `navigator.requestMIDIAccess()` privately
|
||||
(piano, drums, plugin-midi, note-detection's `midi` provider kind), so there is
|
||||
no shared source list, no single permission prompt, and no common redaction of
|
||||
device labels. The onboarding input-setup step (#874/#876/#877) needs a single
|
||||
governed surface to pick and verify a MIDI device per instrument.
|
||||
|
||||
## Why not reuse `audio-input`
|
||||
|
||||
`audio-input`'s source/`source.open` contract is audio-frame-centric:
|
||||
`channelSummary`/`channelCount`/`channelShape`, `requiredChannelShape`, and
|
||||
redaction keyed to audio handles/buffers/samples. MIDI carries discrete messages
|
||||
and has no channel shape. Folding MIDI in would overload the audio contract and
|
||||
its redaction boundary. A sibling domain keeps both contracts clean and lets
|
||||
each evolve independently — the same reasoning that made `audio-input` and
|
||||
`audio-monitoring` siblings rather than one domain.
|
||||
|
||||
## Why core-owned (not plugin-owned)
|
||||
|
||||
An input control plane outlives any one feature; `audio-input` is
|
||||
`core.audio.session`-owned, not owned by a feature plugin. If `input_setup`
|
||||
owned `midi-input`, the domain's lifetime would be coupled to the wizard, and
|
||||
migrating ownership later (every consumer, persistence key, diagnostics schema
|
||||
references the owner) is costly. The domain is `core.midi-input`.
|
||||
|
||||
## Contract
|
||||
|
||||
- **Owner:** `core.midi-input`, kind `provider-coordinator`, safety `sensitive`.
|
||||
- **Public commands:** `inspect`, `list-sources`, `discover`, `select-source`,
|
||||
`open-source`, `close-source`.
|
||||
- **Provider operations:** `source.enumerate`, `source.describe`, `source.open`,
|
||||
`source.close`.
|
||||
- **Events:** `provider-registered`, `provider-unregistered`,
|
||||
`availability-changed`, `sources-changed`, `source-selected`, `source-opened`,
|
||||
`source-closed`.
|
||||
|
||||
### Sources & identity
|
||||
|
||||
Providers register source summaries with `providerId`, a stable `sourceId`, a
|
||||
derived **redaction-safe** `logicalSourceKey` (`providerId::sourceId`),
|
||||
`kind: "midi"`, a label, and `availability`. Persistence and diagnostics use the
|
||||
`logicalSourceKey`, never the human device label.
|
||||
|
||||
### Permission model (Web-MIDI nuance)
|
||||
|
||||
`requestMIDIAccess()` gates the **whole input list**, so **`discover` is the
|
||||
permission boundary** (not `open-source`, as it is for audio). `inspect` /
|
||||
`list-sources` / `select-source` are **prompt-free** and never request access.
|
||||
`discover` records `denied` / `unavailable` outcomes; `open-source` attaches a
|
||||
shared listener session to an already-discovered source and never re-prompts.
|
||||
|
||||
### Sessions
|
||||
|
||||
One shared open session per source across requesters (refcounted); the provider
|
||||
receives `source.close` only after the last requester releases. Live MIDI
|
||||
message delivery (for the "play a note / hit a pad" calibration check) is exposed
|
||||
to in-page consumers via the public `window.slopsmith.midiInput` session handle
|
||||
**only** — never as raw capability events or in diagnostics.
|
||||
|
||||
### Persistence & redaction
|
||||
|
||||
Selected source persists under `slopsmith.midiInput.selectedLogicalSourceKey`.
|
||||
Diagnostics (`slopsmith.midi_input.diagnostics.v1`) carry provider ids, source
|
||||
ids/keys/kinds/availability, the selected key, and open-session keys; device
|
||||
**labels are redacted** and **no raw MIDI messages** are ever included.
|
||||
|
||||
## Split from `midi-control`
|
||||
|
||||
The reserved `midi-control` domain is narrowed to **control mappings only**
|
||||
(CC/pitchbend/note → action routing) and will consume `midi-input` for device
|
||||
access. This spec carves out the device control plane so `midi-control` can stay
|
||||
mappings-only (#882).
|
||||
|
||||
## Consumers (separate issues)
|
||||
|
||||
- `input_setup` onboarding wizard — keys/drums device pick + verify (#876/#877).
|
||||
- `piano` / `drums` plugins — consume `midi-input` instead of private
|
||||
`requestMIDIAccess()` (via the sub-flow issues; legacy retired through bridges).
|
||||
- note-detection's Web-MIDI provider migrates onto `midi-input` (#881).
|
||||
|
||||
## Acceptance
|
||||
|
||||
- Owner registers; appears in the Capability Inspector with the commands above.
|
||||
- `discover` is the only command that triggers `requestMIDIAccess()`;
|
||||
`inspect`/`list-sources`/`select-source` never prompt.
|
||||
- Selection persists across reload by `logicalSourceKey`.
|
||||
- Diagnostics contain no device labels or raw MIDI messages.
|
||||
- A consumer can `discover` → `select-source` → `open-source` → receive live
|
||||
note-on for the calibration check → `close-source` (session refcount releases).
|
||||
|
||||
## Out of scope (follow-ups)
|
||||
|
||||
- `midi-control` mapping/routing domain (#882).
|
||||
- note-detection provider migration onto `midi-input` (#881).
|
||||
- Retiring per-plugin `requestMIDIAccess()` in piano/drums via compatibility
|
||||
bridges (tracked with the sub-flow issues).
|
||||
@@ -0,0 +1,75 @@
|
||||
# Spec 013 — `midi-control` Mappings Domain (the midi-input/midi-control split)
|
||||
|
||||
**Status:** documented future contract (RESERVED — not in the runtime graph) ·
|
||||
**Issue:** #882 · **Depends on:** spec 012 (`midi-input`, delivered) · **Base:** `feedback/main`
|
||||
|
||||
## Summary
|
||||
|
||||
`midi-control` is the planned sibling of `midi-input`: it owns **MIDI control
|
||||
mappings** — routing CC / pitchbend / note messages to *semantic actions* (drum
|
||||
lane, transport command, effect parameter, etc.) — and **consumes `midi-input`**
|
||||
for device access. It does **not** discover, select, or open devices; that is
|
||||
`midi-input`'s job (spec 012, delivered).
|
||||
|
||||
This spec records the **split** so the boundary is unambiguous and the contract
|
||||
is ready for whoever builds the runtime slice. Per project governance
|
||||
(`docs/capability-safety-matrix.md`, `docs/capability-roadmap.md`), a future
|
||||
domain stays **documentation-only until a PR ships its host workflow, a concrete
|
||||
consumer, and tests** — so `midi-control` remains `RESERVED` in
|
||||
`static/capabilities.js` `RESERVED_FUTURE_DOMAINS` until then. This spec does not
|
||||
register a runtime domain.
|
||||
|
||||
## Why split it out
|
||||
|
||||
Before `midi-input` existed, "MIDI" meant two conflated concerns: getting bytes
|
||||
from a device, and mapping those bytes to actions. The reserved `midi-control`
|
||||
entry originally covered both. With `midi-input` delivered as the device control
|
||||
plane, `midi-control` is narrowed to **mappings only** — mirroring how
|
||||
`audio-input` (devices) is separate from `audio-effects`/`audio-mix` (what you do
|
||||
with the signal). Keeping them separate prevents a future god-domain and lets the
|
||||
device plane stabilize independently of mapping semantics.
|
||||
|
||||
## Boundary (normative)
|
||||
|
||||
- **`midi-input` owns:** device discovery (`discover`), source list, selection,
|
||||
open/close sessions, the Web-MIDI permission boundary, redacted device
|
||||
diagnostics. The raw MIDI message stream is delivered to in-page consumers via
|
||||
its session handle.
|
||||
- **`midi-control` will own:** named mappings from MIDI events (note / CC /
|
||||
pitchbend, optionally channel-scoped) to semantic actions, mapping persistence,
|
||||
active-mapping selection, and "learn" capture. It **consumes** a `midi-input`
|
||||
session for the live stream; it never calls `requestMIDIAccess` or enumerates
|
||||
devices.
|
||||
|
||||
## Proposed contract (for the future implementation slice)
|
||||
|
||||
- **Owner:** `core.midi-control` (or a first-party MIDI-control plugin),
|
||||
`multi-provider`, safety `sensitive`.
|
||||
- **Commands:** `list-mappings`, `get-mapping`, `set-mapping`, `delete-mapping`,
|
||||
`activate-mapping`, `inspect`.
|
||||
- **Mapping shape (sketch):** `{ id, label, trigger: { type: 'note'|'cc'|'pitchbend',
|
||||
number?, channel? }, action: { domain?, command?|actionId, params? } }`.
|
||||
- **Learn mode:** open a `midi-input` session, capture the next matching event,
|
||||
and bind it to the pending action (the per-plugin "learn" UIs in drums today
|
||||
are the reference behaviour to generalise).
|
||||
- **Diagnostics:** `slopsmith.midi_control.diagnostics.v1` — mapping summaries +
|
||||
bounded recent activations; **no raw MIDI streams, no device labels**.
|
||||
|
||||
## Intended consumers (promotion trigger)
|
||||
|
||||
The domain should be promoted out of RESERVED when a concrete consumer needs
|
||||
shared mappings, e.g.:
|
||||
- the generic **MIDI control plugin** (`feedback-plugin-midi`) — today an ad-hoc
|
||||
event→action mapper; the canonical first adopter.
|
||||
- **drums** note→lane mapping + "learn mode" (`feedback-plugin-drums`,
|
||||
`feedback-plugin-drum-highway-3d`) — currently per-plugin; could adopt
|
||||
`midi-control` to share mapping logic once the contract is proven.
|
||||
|
||||
Until such a consumer-driven slice exists (with host workflow + tests), this
|
||||
remains a documented contract only.
|
||||
|
||||
## Out of scope
|
||||
|
||||
- Any runtime registration / handlers (governance: no premature domain).
|
||||
- Migrating the drums/keys per-plugin mapping now — deferred to the consumer slice.
|
||||
- The device plane — owned by `midi-input` (spec 012, done).
|
||||
Reference in New Issue
Block a user