You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Per-phrase tiers 1/4: persist per-tier phrase data through the sloppak save path #33
Part of #3. First: the storage foundation the other sub-issues write into.
Today _save_sloppak repopulates each phrase's levels[] by repeating the editor's single flat note/chord/anchor list across every level, which makes the mastery slider a no-op for editor-saved packs (see the CHANGELOG entry quoted in #3).
Scope
Give the editor's in-memory model a place to hold distinct per-tier content for a phrase (a full chart down to simplified versions, mirroring RS2014 <level difficulty="N"> blocks).
Update _save_sloppak to persist the authored per-tier data instead of repeating the flat list across every levels[] entry; a phrase with no authored tiers keeps today's behavior.
Check the on-disk shape against the pack format (feedpak-spec, lib/song.pyPhrase/PhraseLevel) before writing; any new manifest key must land in the spec first.
Round trip: load a sloppak with distinct levels, save without edits, and confirm the levels are preserved.
Acceptance criteria
A sloppak with distinct per-level data survives load and save unchanged.
A phrase without authored tiers still saves exactly as before.
Covered by backend tests.
Out of scope
Creating tiers (2/4 and 3/4) and the end-to-end slider check (4/4).
@pullfrog please work on this issue. It is the first sub-issue of #3 and has no dependencies. Check the on-disk shape against feedpak-spec before writing any new field; any new manifest key must land in the spec first, so stop and say so if it would need one. Keep the PR scoped to this sub-issue only and do not start the dependent sub-issues. Open a PR against main; do not merge it.
PR #47 is open against main (unmerged): #47 — storage foundation for #33, no tier-authoring UI.
What changed.phrases[].levels[] is already the spec's per-phrase tier array, but the save path overwrote every level with one flat-chart slice, so a pack's distinct tiers were destroyed on any edit (and the highway's mastery filter then rendered stale per-level notes, making all edits vanish). Load now hands the editor phrases[].tiers[] in the editor's note/chord shape (_phrase_with_tiers), and save writes it back to the existing on-disk levels[] (_authored_phrase_levels): authored lower tiers persist verbatim, the top tier re-slices from the flat chart per core's top-level rule, and a phrase with no tiers keeps the old flat behavior. No new pack field. On the frontend, tier content rides the beat-primary tempo model (lift/reproject) and the save strip clears beat/beatEnd one level deeper, and replacing a chart drops tier content while keeping the difficulty ladder (undo stays exact).
Review findings addressed. Fixed a typo I introduced in the hoisted wire converter (slide_unpick_to → slide_unpitch_to, which silently reset slu to -1 for flat and tier notes) and added a mechanical guard that every _NOTE_TECH_FIELDS name reaches the wire — verified it fails when the typo is reintroduced. Also: a tier member that can't convert is skipped instead of failing the whole save (that payload path is new and client-supplied), per-tier handshapes now get the arrangement's chord_id bound, unknown keys on an incoming level survive as on the flat path, a repeated difficulty keeps the first tier, and the two weak frontend tests were made non-tautological.
Not included (unchanged from the plan): tier-authoring UI, and per-tier MusicXML export — _build_arrangement_xml ignores phrases entirely, which is pre-existing and no route consumes it.
Activity
pullfrog commentedon Oct 2, 2026
cchacs commentedon Oct 2, 2026
@pullfrog please work on this issue. It is the first sub-issue of #3 and has no dependencies. Check the on-disk shape against
feedpak-specbefore writing any new field; any new manifest key must land in the spec first, so stop and say so if it would need one. Keep the PR scoped to this sub-issue only and do not start the dependent sub-issues. Open a PR againstmain; do not merge it.Generated by Claude Code
pullfrog commentedon Oct 2, 2026
PR #47 is open against
main(unmerged): #47 — storage foundation for #33, no tier-authoring UI.What changed.
phrases[].levels[]is already the spec's per-phrase tier array, but the save path overwrote every level with one flat-chart slice, so a pack's distinct tiers were destroyed on any edit (and the highway's mastery filter then rendered stale per-level notes, making all edits vanish). Load now hands the editorphrases[].tiers[]in the editor's note/chord shape (_phrase_with_tiers), and save writes it back to the existing on-disklevels[](_authored_phrase_levels): authored lower tiers persist verbatim, the top tier re-slices from the flat chart per core's top-level rule, and a phrase with no tiers keeps the old flat behavior. No new pack field. On the frontend, tier content rides the beat-primary tempo model (lift/reproject) and the save strip clearsbeat/beatEndone level deeper, and replacing a chart drops tier content while keeping the difficulty ladder (undo stays exact).Review findings addressed. Fixed a typo I introduced in the hoisted wire converter (
slide_unpick_to→slide_unpitch_to, which silently resetsluto -1 for flat and tier notes) and added a mechanical guard that every_NOTE_TECH_FIELDSname reaches the wire — verified it fails when the typo is reintroduced. Also: a tier member that can't convert is skipped instead of failing the whole save (that payload path is new and client-supplied), per-tier handshapes now get the arrangement'schord_idbound, unknown keys on an incoming level survive as on the flat path, a repeated difficulty keeps the first tier, and the two weak frontend tests were made non-tautological.Verification.
pytest: 468 passed, 5 skipped (444 baseline + 24 new).npm test: 316 files, 0 failures.npm run lint: 0 errors, 3 pre-existing warnings.Not included (unchanged from the plan): tier-authoring UI, and per-tier MusicXML export —
_build_arrangement_xmlignoresphrasesentirely, which is pre-existing and no route consumes it.big-pickle(free) | 𝕏