Stacked on #34 (→ #33 → #31 → #30). Merge from the top down: this one first, then #34, #33, #31, #30.
Line breaks were always placed by songtrace: midway between lines, and recomputed around edited lines. There was no way to put one where the singer actually breathes. This makes them movable on the sheet. It's the last phase-3 item.
On the sheet
Markers: the breaks before and after the current line are drawn as ↵ markers, dashed grey when automatic and solid aqua when moved by hand.
Dragging: a marker snaps to the beat grid and stays between the end of the line before it and the start of the line it introduces.
Automatic again: double-clicking a marker returns it to automatic. It's shown mid-gap until saved.
History and saving: break moves share the line's undo/redo and its UNSAVED state. SAVE LINE saves the notes only if they changed and the breaks only if they changed, so moving a break alone never marks a line as edited, and both lines keep following rebuilds.
Window: the sheet's view now always includes the line's breaks. Before, the "after" break often sat just outside it.
Shortcut panel: gains a "Line break" group.
How it's kept
Stored: moved breaks are a breaks map in edits.json, keyed by the line each comes before.
Lyric changes: a break follows its line like line edits do. If that line's words change, its break is dropped, since there's nothing left for it to separate.
Rebuilds: applying edits uses a moved break in place of the computed one, clamped so it never lands inside either line's notes even if those notes move later.
API:
the review lists each break as {line, time, moved}
PUT /api/jobs/{id}/breaks takes {line: seconds | null}, validated
the charts are rewritten in place
merge now passes untouched notes through as they are, instead of rebuilding them from start and end. The new break test caught that the rebuild was adding float noise to their durations.
Testing
New tests:
edits: validation, a moved break kept in place, clamping out of either line, breaks following their lines and the file kept for a break alone.
edit-model: placeBreak snapping and clamping.
Web: a move reaching the chart's - beat line and the review, not marking the line edited, null resetting it, bad input refused.
In Chromium on a seeded song with real breaks:
dragging a break marked the line unsaved; undo and redo
the saved break was at its new time, marked as moved, with the line not edited
double-clicking it back to automatic restored the generated time
a note edit and a break move saved together
no script errors
Full suite passes, except test_version (needs the package installed).
**Summary generated by LLM agent**
Stacked on #34 (→ #33 → #31 → #30). Merge from the top down: this one first, then #34, #33, #31, #30.
Line breaks were always placed by songtrace: midway between lines, and recomputed around edited lines. There was no way to put one where the singer actually breathes. This makes them movable on the sheet. It's the last phase-3 item.
## On the sheet
- **Markers:** the breaks before and after the current line are drawn as ↵ markers, dashed grey when automatic and solid aqua when moved by hand.
- **Dragging:** a marker snaps to the beat grid and stays between the end of the line before it and the start of the line it introduces.
- **Automatic again:** double-clicking a marker returns it to automatic. It's shown mid-gap until saved.
- **History and saving:** break moves share the line's undo/redo and its UNSAVED state. SAVE LINE saves the notes only if they changed and the breaks only if they changed, so moving a break alone never marks a line as edited, and both lines keep following rebuilds.
- **Window:** the sheet's view now always includes the line's breaks. Before, the "after" break often sat just outside it.
- **Shortcut panel:** gains a "Line break" group.
## How it's kept
- **Stored:** moved breaks are a `breaks` map in `edits.json`, keyed by the line each comes before.
- **Lyric changes:** a break follows its line like line edits do. If that line's words change, its break is dropped, since there's nothing left for it to separate.
- **Rebuilds:** applying edits uses a moved break in place of the computed one, clamped so it never lands inside either line's notes even if those notes move later.
- **API:**
- the review lists each break as `{line, time, moved}`
- `PUT /api/jobs/{id}/breaks` takes `{line: seconds | null}`, validated
- the charts are rewritten in place
`merge` now passes untouched notes through as they are, instead of rebuilding them from start and end. The new break test caught that the rebuild was adding float noise to their durations.
## Testing
- New tests:
- `edits`: validation, a moved break kept in place, clamping out of either line, breaks following their lines and the file kept for a break alone.
- `edit-model`: `placeBreak` snapping and clamping.
- Web: a move reaching the chart's `- beat` line and the review, not marking the line edited, `null` resetting it, bad input refused.
- In Chromium on a seeded song with real breaks:
- dragging a break marked the line unsaved; undo and redo
- the saved break was at its new time, marked as moved, with the line not edited
- double-clicking it back to automatic restored the generated time
- a note edit and a break move saved together
- no script errors
- Full suite passes, except `test_version` (needs the package installed).
Line breaks were always placed by songtrace, midway between lines and
recomputed around edited ones, so a break could not be put where a singer
breathes. The sheet now draws the breaks either side of a line as markers
that drag along the beat grid, kept between the lines they separate, and
double-click back to automatic. Break moves share the line's undo history
and SAVE LINE, which saves the notes only if they changed, so moving a
break never locks either line's notes.
Moved breaks are stored in edits.json keyed by the line each comes before,
follow their lines when the lyrics change, and win over the computed break
on every rebuild, clamped so a break never lands inside a line's notes.
The review lists each break with that line and whether it was moved, and
PUT /api/jobs/{id}/breaks takes {line: seconds | null}. merge now passes
untouched notes through as they are rather than rebuilding them, which
added float noise to their durations.
branderson
approved these changes 2026-10-14 18:54:16 -07:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary generated by LLM agent
Stacked on #34 (→ #33 → #31 → #30). Merge from the top down: this one first, then #34, #33, #31, #30.
Line breaks were always placed by songtrace: midway between lines, and recomputed around edited lines. There was no way to put one where the singer actually breathes. This makes them movable on the sheet. It's the last phase-3 item.
On the sheet
How it's kept
breaksmap inedits.json, keyed by the line each comes before.{line, time, moved}PUT /api/jobs/{id}/breakstakes{line: seconds | null}, validatedmergenow passes untouched notes through as they are, instead of rebuilding them from start and end. The new break test caught that the rebuild was adding float noise to their durations.Testing
edits: validation, a moved break kept in place, clamping out of either line, breaks following their lines and the file kept for a break alone.edit-model:placeBreaksnapping and clamping.- beatline and the review, not marking the line edited,nullresetting it, bad input refused.test_version(needs the package installed).Line breaks were always placed by songtrace, midway between lines and recomputed around edited ones, so a break could not be put where a singer breathes. The sheet now draws the breaks either side of a line as markers that drag along the beat grid, kept between the lines they separate, and double-click back to automatic. Break moves share the line's undo history and SAVE LINE, which saves the notes only if they changed, so moving a break never locks either line's notes. Moved breaks are stored in edits.json keyed by the line each comes before, follow their lines when the lyrics change, and win over the computed break on every rebuild, clamped so a break never lands inside a line's notes. The review lists each break with that line and whether it was moved, and PUT /api/jobs/{id}/breaks takes {line: seconds | null}. merge now passes untouched notes through as they are rather than rebuilding them, which added float noise to their durations.