Skip to content

feat: MIDI, bring-up machine, oscillator/i2c/cloud-sync, serial-read fix + node diagnostics - #93

Merged
xiduzo merged 12 commits into
mainfrom
feat/midi-bringup-and-serial-fix
Jul 18, 2026
Merged

feat: MIDI, bring-up machine, oscillator/i2c/cloud-sync, serial-read fix + node diagnostics#93
xiduzo merged 12 commits into
mainfrom
feat/midi-bringup-and-serial-fix

Conversation

@xiduzo

@xiduzo xiduzo commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Branch bundle of several subsystems plus a runtime-fault-surfacing feature. Split into logical commits; each subsystem is independent, so review by commit.

Features

  • MIDI node — in/out across browser (Web MIDI), desktop (midir), and Arduino (serial MIDI.h).
  • Shared sans-IO bring-up state machine — probe→flash→connect policy shared by both hosts.
  • Oscillatorrandomwalk + perlin waveforms (device matches live sample-for-sample).
  • I2C — single-source byte-decode descriptor; now surfaces unACKed reads as a node error (see below).
  • Stepper — config updates; hide unsupported template.
  • Cloud-sync — unified capability sync, drop per-provider hooks.
  • Collab — flow-access roles + auth-client session helpers.

Fixes

  • Desktop serial-read starvation — the actor's run-loop drained the whole queue before reading serial, so a fast outbound wake loop (60fps oscillator) + a blocking macOS flush() starved every input (buttons/I2C/NFC). Fixed by pumping the port after each message + dropping the per-write flush.
  • CI — install libasound2-dev on Linux for alsa-sys (midir); satisfy rust-1.97 clippy pedantic lints.

Node diagnostics (new runtime→UI health channel)

A NACKing I2C sensor used to fail silently (monitor stuck at 0). Root cause: the board streams an empty I2C_REPLY + "I2C: Too few bytes received", and the codec dropped the empty reply (len < 8), so the node never saw the fault.

  • Codec: parse zero-data replies (len >= 7) so the fault reaches the target node (routed by address+register).
  • New channel: Effects.node_diagnostics → compile-forced EffectsSink::report_diagnostic hook in both hosts (ADR-0008/0009 pattern). Nodes raise via RuntimeContext::report_diagnostic / clear_diagnostic.
  • I2C node: on a short read, raises an error (attributed by address, keeps last good value), clears on recovery — raise/clear only on a transition, so a per-poll NACK doesn't spam.
  • UI: reuses the existing NodeContainer error/warning badge — the I2C node shows "No response from 0x29: got 0 of 8 bytes. Check wiring…". Works identically on desktop (Tauri node-diagnostic) and in-browser (reactor).

Verification

590 core tests pass (incl. a new raise→dedup→clear test); workspace clippy clean (-D warnings -W pedantic); wasm runtime compiles; frontend tsc clean; browser effects-sink conformance test updated + passing.

🤖 Generated with Claude Code

xiduzo and others added 12 commits July 18, 2026 21:17
Single Midi node handling both directions:
- browser via Web MIDI (MidiPerformer, the twin of the desktop MidiManager)
- desktop via midir (runtime/midi.rs on the actor thread)
- Arduino via serial MIDI.h codegen

Adds midi_wiring/collect_midi_listeners (no per-topic reconcile — every
matching listener receives every message), a MidiSend CloudRequestKind
intercepted by each host before the cloud performer, and the NodeEmission
shared_declarations/setup/loop dedup primitive for codegen. Validate warns
that the serial-MIDI emitter claims the board's primary UART.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Lift the bring-up POLICY (probe -> flash-if-missing -> connect -> reconnect,
and the disconnected/connecting/flashing/connected/error transitions) into
microflow_core::bringup, shared by both hosts. Desktop (hardware/mod.rs) and
browser (board-controller.ts) become thin adapters that feed the machine
Web-Serial/serial events and perform the actions it returns. web-serial.ts is
reduced to transport primitives (probeFirmata / probeAfterFlash); the wasm
BringUpMachine binding exposes the machine to the browser.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Both waveforms use the same sin-hash lattice in the live runtime and the
generated sketch, so the device matches the live preview sample-for-sample.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the I2C reply fold arithmetic into one shared ByteDecode descriptor in
config::i2c_device (fold_bytes); the runtime interprets it and the sketch
emitter transcribes it to C++, with codegen/parity.rs pinning the two. No
behavioural change.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Hide the stepperPosition template: flashed StandardFirmata has no
AccelStepper support, so the motor never moves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace use-llm-sync/use-mqtt-sync with a single cloud-capability-sync
module reading one HostSnapshot (brokers/providers/figma); provider status
now lives on the store. The FlowUpdateDispatcher reads the shared snapshot.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add flow-access/flow-role routers (per-flow access checks) and extract
getSession/getCustomerState helpers on the auth client, wiring them through
the flow/login/profile routes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The single-threaded runtime actor owns the FlowRuntime and the serial port.
Its loop drained ALL queued ActorMsgs before reading the port once, and each
outbound write blocked on flush() (macOS tcdrain). With a 60fps oscillator
arming a _tick every ~16ms, per-wake processing exceeded the tick period, so
the message queue never emptied, pump_port() never ran, and feed_bytes was
never called — every input (buttons, I2C, NFC) went silent while outbound
flooded.

Fix: read the port after each handled message (no message volume can starve
reads), and drop the per-write flush (write_all sends in order; synchronous
drain isn't needed).

Note: mid-debug — buttons recover but I2C streaming is still under
investigation; kept as its own commit for easy revert/iteration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Wire the desktop MidiManager (runtime/midi.rs) into the actor's module tree.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The MIDI node pulls midir, whose Linux backend (alsa-sys) needs the ALSA
headers to build. All three Linux CI jobs (clippy, test, build-tauri) failed
on `failed to run custom build command for alsa-sys`; add the dev package
alongside the existing libudev-dev.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CI clippy (rust 1.97) is stricter than local (1.95):
- backtick FortySevenEffects in the midi emitter doc (doc_markdown)
- if let/else over a two-arm match in the midi step sequencer (single_match)
- allow many_single_char_names on the noise-lattice fns (conventional t/i/f/a/b)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The alsa fix let CI clippy compile the desktop crate on Linux for the first
time, surfacing one more pedantic lint (must_use_candidate) that macOS/local
builds also have but CI now enforces workspace-wide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@xiduzo
xiduzo merged commit 9c09c7e into main Jul 18, 2026
7 checks passed
@xiduzo xiduzo changed the title feat: MIDI node, shared bring-up machine, oscillator/i2c/cloud-sync + serial-read fix feat: MIDI, bring-up machine, oscillator/i2c/cloud-sync, serial-read fix + node diagnostics Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant