Skip to content

fix: adding a beat could wipe a beat timeline after "Set amount in measure" - #577

Open
FelipeDefensor wants to merge 1 commit into
devfrom
fix/beat-pattern-extension-crash
Open

fix: adding a beat could wipe a beat timeline after "Set amount in measure"#577
FelipeDefensor wants to merge 1 commit into
devfrom
fix/beat-pattern-extension-crash

Conversation

@FelipeDefensor

Copy link
Copy Markdown
Collaborator

Summary

"Set amount in measure" puts an arbitrary number of beats in a measure — that is the point of the feature. So beats_in_measure[-1] can legitimately hold more beats than the beat pattern prescribes for that slot.

get_extension_from_beat_pattern treated that as impossible and raised:

ValueError: More beats on starting measure than found in the iterator

and extend_beats_in_measure only recognised an exactly full measure as complete, so it would have grown the over-full measure further rather than starting a new one.

The data loss. Adding a beat in that state raises out of recalculate_measures. TimelineUIs.on_timeline_command recovers by restoring the snapshot it took before the command — but restoring re-runs recalculate_measures, hits the same exception, and aborts after clearing the timeline. The user gets an error dialog and an empty beat timeline; every beat is gone.

Both places now treat a measure at or over its prescribed size as full, so the extension starts a new measure.

Reproducing on dev

  1. Beat timeline with beat pattern 4, add 24 beats → beats_in_measure == [4, 4, 4, 4, 4, 4]
  2. Select a beat in the 5th measure, "Set amount in measure" → 5[4, 4, 4, 4, 5, 3]
  3. Delete the last 3 beats, so the over-full measure becomes the last one → [4, 4, 4, 4, 5]
  4. Add a beat → "Command failed" dialog, and the timeline is left with 0 beats

With this change step 4 gives [4, 4, 4, 4, 5, 1] and all 22 beats are present.

Test plan

  • test_add_beat_when_last_measure_is_fuller_than_beat_pattern — end-to-end through the commands, walks the steps above; raises on dev
  • test_starting_measure_fuller_than_beat_pattern — the previously-raising case; get_extension_from_beat_pattern was otherwise untested, so the two neighbouring cases (emptier / exactly as full) are pinned alongside it
  • Beat suites green, and the rest of the suite unaffected (the 3 tests/test_app.py failures under pytest -n auto reproduce on dev without this change and pass serially)
  • Randomised fuzz over the beat commands (25 seeds x 200 steps of set/reset measure number, set beats in measure, add, delete, distribute): 12 of 25 seeds hit the crash on dev, 0 of 25 with this change

Found by fuzzing while reviewing #480; independent of it.

🤖 Generated with Claude Code

"Set amount in measure" puts an arbitrary number of beats in a measure,
so beats_in_measure[-1] can hold more than the beat pattern prescribes
for that slot. get_extension_from_beat_pattern treated that as impossible
and raised, and extend_beats_in_measure only recognised an exactly-full
measure as complete.

Adding a beat in that state raised out of recalculate_measures. The
command handler recovers by restoring the snapshot it took beforehand,
but restoring re-runs the same code, so the recovery failed too and left
the timeline empty -- every beat on it was lost.

Both places now treat a measure at or over its prescribed size as full,
so the extension starts a new measure instead.
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