Skip to content

0.8.0: one wait experience everywhere, and STOP means stop

Choose a tag to compare

@monzta1 monzta1 released this 02 Sep 05:44
· 144 commits to main since this release

Watch 0.8.0 do its thing: one sentence, an empty FM9 slot, six minutes, a complete 8-scene Judas Priest preset. Unedited: https://www.youtube.com/watch?v=jCNAZyyt5o8

One wait experience, everywhere. The COMMAND box learned to count seconds,
listen for a heartbeat and offer a way out months before the rest of the app
did; every other path that could run for more than a few seconds still hid
behind a frozen line of text. This release moves all of them onto the same
machinery, and makes STOP mean stop.

Added

  • The claude CLI backend streams. Plans stream their action count and
    chat streams its words through --output-format stream-json, so the
    "31 changes written" counter finally fires on the zero-configuration
    default install instead of only behind an OpenAI-compatible router. The
    wire format was verified against the real CLI, not assumed.
  • STOP stops the backend. Abandoning a plan, chat, build or source read
    kills the planner subprocess on the server instead of leaving it burning
    for minutes while holding the settings lock. A request that does have to
    wait behind an earlier one now says QUEUED instead of pretending to work.
  • BUILD FROM A SOURCE streams. Reading narrates its stages off the wire
    (fetching, downloading audio, the first-run whisper model, transcribing,
    extracting) with an elapsed count and STOP; building shows the same
    changes-written counter as the COMMAND box. Both used to be a static line
    over minutes of work, the exact failure the plan stream was built to end.
  • FIX IT goes through the streaming planner, with the banner, the count
    and STOP, instead of a bare ASKING... over a multi-minute call.
  • The preset scan counts. Reading all 512 slot names is about fifteen
    seconds of MIDI; the popover, RESCAN and NAMES now count n/512 through it
    instead of showing "nothing matches" over an empty list, which read as
    "your unit is empty".
  • The health scan names the scene it is standing on, so the noises the
    rig makes during a scan are narrated rather than left unexplained, and the
    button counts SCENE n/8.
  • GIG MODE is on the page. A header pill shows the performance lockout
    and toggles it; it used to exist only as an environment variable and an
    API, so the page looked normal while every control failed one refusal at a
    time, and the refusal then told a guitarist to POST JSON.
  • The blast-radius sweep says so: mapping which scenes share channels steps
    the rig through all eight scenes audibly, and the log now names that read
    when it happens.
  • The LINK pill tracks the cable, both ways, in about a second. The
    server keeps one CoreMIDI client with a real notify callback on a runloop
    thread, which is the thing that actually keeps a macOS process's view of
    the MIDI bus alive (two earlier fixes for this are recorded, disproven,
    in KNOWN_QUIRKS), and GET /api/link/stream pushes presence changes to
    the page the moment they happen. Verified across six live plug/unplug
    cycles on real hardware.

Fixed

  • A build aimed at an empty slot lays its own foundations. A 135-action
    plan used to halt at "ADD amp" on a freshly erased slot and tell the
    player to go and press BUILD A STARTING CHAIN themselves: the tool knew
    the problem, knew the remedy, owned the code for it, and handed the work
    back. A transmit containing add_block now builds the starting chain
    itself when the loaded slot is empty (announced on the plan card before
    confirmation, reported as its own row in the results), and an add for a
    block already present counts as satisfied instead of halting everything
    after it.
  • Erasing is one confirmation now; the typing test is retired. The
    typed-name echo refused legitimate attempts over invisible double
    spaces, then over a machine-built title, then got fed the slot number,
    all in one evening. What survives is the part that protects: ERASE only
    arms once the slot's name has been read and shown, the single dialog
    names the slot and exactly what it holds, and the page sends the name it
    displayed for the server to match against flash, so a slot that changed
    since it was shown still refuses. API callers keep the name contract,
    spacing and case forgiven, and a typed slot number gets a reply naming
    the actual ask instead of a bare refusal.
  • Every slot label now leads with the number on your unit. The header
    pill said 159 while every label below said "158 (FM9-Edit 159)", so the
    app looked like it disagreed with itself about which preset was loaded.
    Labels are "159 (wire 158)" everywhere now: the front-panel number
    first, the MIDI wire number named for what it is in the bracket. One
    function owns the format, so nothing can drift back. The SAVE dropdown
    also fills its names right after startup instead of saying "name not
    read" until NAMES was pressed.
  • The SAVE panel contradicted its own dropdown after an unsaved build.
    The "Loaded:" line printed the edit buffer's name as though it were the
    slot's, directly above a dropdown showing what flash actually holds, and
    the two read as mismatching data. They are two true facts about two
    different things; when they differ the panel now names both and says
    which is which: what the slot holds, what your unsaved edits are called,
    and that SAVE writes the second over the first.
  • A refused action was missing from the live transmit count. The
    validation branch skipped the progress callback, so the SENDING counter
    stuck at n-1 of N while the final banner said otherwise. Found by running
    the stream against the real server, not by reading the code, which had
    looked fine. Refusals now count as failed steps.
  • ERASE refused legitimate attempts and looked broken. The typed-name
    confirmation demanded an exact match against names that carry internal
    double spaces the eye cannot see and machine-built titles nobody retypes
    from memory; two real attempts in a row were refused, and the refusal
    landed only in the LOG panel, far from the button. Spacing runs and case
    are forgiven now (the name itself is not), refusals are logged
    server-side, and both the refusal and the success are announced in the
    strip. (The typing itself was retired later the same evening; see the
    one-confirmation entry above.)
  • A failed action was pointed at, not named. "1 did not apply, marked
    above" left the player hunting through a hundred folded cards, twice in
    one evening. The outcome banner now names each failure with its reason,
    the card list opens itself and scrolls to the first failed card, and the
    server logs every refused action so a failure can be diagnosed without
    asking the player to read their browser back.
  • SHOW LOG sometimes needed several presses. The working strip rebuilt
    its entire HTML every second, so the button being pressed was destroyed
    between mousedown and mouseup and the click fell into the gap. The strip
    is built once now and only its text updates.
  • The strip vanished at the finish, so completion looked like nothing.
    It now holds the verdict for a few seconds, in the one place that cannot
    be scrolled away from: DONE in green with the count (and the stored slot
    when one was written), partial sends in amber, failures in red, and
    BUILD COMPLETE when a plan lands. A click dismisses it early.
  • A finished build looked like nothing happening. The completion note
    landed in the chat transcript while the page scrolled you to the plan
    panel, so the one line saying "the build worked, TRANSMIT is next" sat
    exactly where you no longer were. The plan panel now opens with its own
    verdict strip: how many changes, that nothing has been sent, that
    TRANSMIT is the next step, and the truth about UNDO for store plans.
  • The preset dropdown kept the overwritten preset's name after a stored
    plan.
    The slot-name cache was only ever invalidated by RESCAN, NAMES,
    rename and erase; a store inside a transmitted plan never told it. The
    store result already carries the slot's new name, so the cache entry is
    now corrected in place (no rescan) and the page re-pulls its lists after
    any transmit that stored. The SAVE button's full rescan-after-save is
    gone for the same reason: it spent seconds of MIDI to learn a name the
    store result had already delivered.
  • The transmit banner lied after a stored plan. "Your presets are
    untouched; UNDO covers what landed" was shown after a plan whose store had
    just overwritten a flash slot, false on both counts and caught live on the
    first real build through the new pipeline. The outcome copy now checks
    whether a store landed and names the overwritten slot when one did.
  • Gig mode could audibly walk all eight scenes mid-song. The shared-
    channel sweep ran automatically when the preset changed and was not gig
    gated, so a front-panel preset change during a set had the tool stepping
    scenes while someone played through it. It now refuses during gig mode and
    answers from cache only.
  • HOLD A and HOLD B stayed dead for the rest of the session after any undo
    or recall: the recall disabled all five buttons and the refresh only ever
    re-enabled three.
  • UNDO and RECALL say UNDOING.../RECALLING... while a forty-value restore
    runs; the preset pill shows a busy state while the unit switches.

Recipe sharing stops being code and starts being a thing that exists. The
worker and schema had been written and tested since 0.4.x and never deployed,
so the tone database existed as source and as nothing else: every recipe
anybody wrote went into the local outbox and stayed there.

Added

  • The sharing service is live, on Cloudflare Workers and D1. It holds an
    inbox and a counter and never content: recipes live in this repository's
    recipes/ folder. That split decides the failure mode. If the worker is
    down, browsing and using recipes still work, only submission and ranking
    pause, and the client holds both until it returns. Nothing anybody writes
    depends on it being up.
  • AUTO_PUBLISH, off by default. On, a submission is committed straight
    into recipes/ and is live immediately, with no human step. Every
    submission is recorded in D1 either way, so it is one flag to undo with
    nothing lost. Be clear-eyed about what it means: /submit is
    unauthenticated, so with it on, anyone who can reach the worker can write a
    file into a public repository. That is a deliberate choice while there are
    no users rather than an oversight.
  • A note when a recipe was made on different firmware. Recipes name models
    rather than numbering them, so a step resolves through the loading rig's own
    roster and a model that does not exist there is refused rather than becoming
    its neighbour. That covers the structural risk and not the audible one:
    Fractal revises voicings between releases, and tested_firmware was being
    recorded and shown to nobody. The plan now says so before you transmit.
  • service/wrangler.toml, which did not exist, so there was nothing to deploy
    with.

Fixed

  • The app could not find the service the docs told you to configure.
    share.endpoint() read only os.environ, while the planner and the store
    whitelist both fall back to .env. So the documented setup left sharing
    silently dark: no endpoint, recipes queueing forever, nothing anywhere
    saying why. A configuration that fails closed AND says nothing is the worst
    of both.
  • A submitted recipe could carry anything. The validator checked the
    envelope and never looked inside a step, so steps: [1,2,3] passed, and
    publishing writes the whole body, so any invented top-level key was
    preserved verbatim into the repository. On an unauthenticated endpoint with
    auto-publish on, that is "anyone may write arbitrary JSON into a public
    repo". Now: known keys only, every step an object whose kind is a real
    action, no invented keys inside steps, text bounded. store is refused
    outright, being the one action that writes to flash.
  • Publishing refuses to overwrite. Not moderation, integrity: without it
    anyone could post a recipe named after a curated tone and silently replace
    it.
  • The suite no longer reads the developer's real .env when testing sharing.
    The .env fallback leaked within a minute of being added, into a test
    asserting sharing was local only.