Skip to content

fix: verify flash writes, gate setup on firmware, sticky guidance cards - #36

Merged
joshuagruenstein merged 3 commits into
mainfrom
fix-device-setup
Aug 5, 2026
Merged

joshuagruenstein merged 3 commits into
mainfrom
fix-device-setup

Conversation

@joshuagruenstein

Copy link
Copy Markdown
Member

The bug

A newly flashed ting: dictation worked over 3.5mm, green/orange never did. Over USB everything worked.

Diagnosed by recording the raw line-in with ffmpeg and running the production decoder offline over the capture (new --decode-wav flag), with serial events as ground truth. A 5ms frequency-ridge analysis showed the device acoustically playing more symbols than the payload ever queues — a "beacon" with five S0 chirps, a phantom modeChanged(4) that was really (2,3,1,3) on the wire.

Root cause: firmware below 1.0.8 plays the current slot sample on squeeze and on button presses (the ting is an instrument, and our payload chains the stock callback for its bookkeeping). The slots hold chirp symbols, so stock playback sprays extra symbols into the middle of every queued event word. Beacons are 4 identical symbols and survive on FEC; diverse event words die. Serial is unaffected — hence USB looking healthy. Confirmed by A/B: after the firmware upgrade, all three event types decode.

Fixes

Firmware is part of setup. Flash EP on a ting tingle hasn't seen at 1.0.8 routes into the guided upgrade flow (which reinstalls the payload at the end). Firmware state is tracked per device serial — the global flag had marked this brand-new unit "already 1.0.8" because a different ting had been upgraded, which is exactly why the trap was invisible.

Verified flashing. Every write is read back and checksummed before the eject, plus a sync. A truncated/failed/reverted flash now fails loudly ("Flash verification failed — …") instead of reporting success and leaving a dead device. This also would have caught the earlier incident in this same session, where a stale 4096-byte payload sat on the device while Flash EP claimed success.

Dry-bus config.json now ships with every flash (SAMPLE-only presets).

Guidance UX. NSPanel hides itself on app deactivate, so the firmware ritual card disappeared the instant focus moved — user left squeezing the handle with no instructions. Cards are now .statusBar level, hidesOnDeactivate = false, all-Spaces. Added a one-flow-at-a-time guard (a re-click raced two flows into simultaneous "failed" and "succeeded" popups) and fixed the copy: the button needs a true rapid double-click, not two deliberate presses.

Testing

  • 358 assertions (new FlasherTests: verification mismatch cases incl. the exact truncation signature, dry-preset invariants, per-serial firmware bookkeeping) + payload tests green.
  • Live-verified on hardware: full firmware flow, post-upgrade acoustic A/B, and a focus-stealing flash to confirm cards stick.

Deferred

Payload inter-word pacing — rapid back-to-back presses still clip chirp edges at word boundaries even on 1.0.8 (isolated presses are clean). Real, but wants its own PR tested on both devices.

🤖 Generated with Claude Code

joshuagruenstein and others added 3 commits July 14, 2026 18:09
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Root-caused a new ting whose buttons never worked over the 3.5mm audio
path while dictation did (acoustic capture + offline decode of the
recording, 2026-08-05): firmware below 1.0.8 plays the current slot
sample on squeeze and button presses -- and the slots hold our chirp
symbols -- so the stock engine injects extra symbols into the middle of
every event word. Beacons survived on FEC; every button word died.
Serial was unaffected, which is why USB looked fine.

- Firmware is part of setup: Flash EP on a ting tingle has not seen at
  1.0.8 routes into the guided upgrade flow, which reinstalls the
  payload at the end. Firmware bookkeeping is now PER DEVICE SERIAL --
  a global flag had marked this brand-new unit as already upgraded
  because a different ting was.
- Every Flash EP write is read back and checksummed before the eject,
  and the volume is synced; a truncated or reverted flash now fails
  loudly instead of reporting success and leaving a dead device.
- Flash EP also ships a dry-bus config.json (SAMPLE-only presets).
- Guidance cards stick: NSPanel hid itself on app deactivate, so the
  firmware ritual card vanished the moment focus moved -- leaving the
  user squeezing the handle with no instructions. Plus a one-flow-at-a-
  time guard (a re-click raced two flows into dueling failed/succeeded
  popups) and copy that says the button needs a true rapid double-click.
- Test tooling: --decode-wav runs the production decoder over a line-in
  recording, --dump-flash-bytes emits the exact bytes Flash EP ships
  (both were decisive in this diagnosis).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@joshuagruenstein
joshuagruenstein merged commit 37629bb into main Aug 5, 2026
2 checks passed
@joshuagruenstein
joshuagruenstein deleted the fix-device-setup branch August 5, 2026 16:40
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