Phase 1 of turning the review viewer into an editor that replaces Yass. This is the part that keeps manual edits safe across every kind of rebuild, including REBUILD ALL FROM… and a rebuild from scratch. There's no editing UI yet; that's the next phase and builds on this API.
How edits are kept
Separate file: edits live in work/<slug>/edits.json. No stage lists it as an output, so no rebuild removes it. Deleting the song does.
Whole lines: an edit is a whole lyric line as the user left it: every note's start, duration, pitch, syllable and kind. Editing one note saves the line.
Seconds, not beats: a rebuild that picks a new BPM re-snaps the edit instead of breaking it.
On every run, grid lays the edits over the freshly generated notes before snapping to beats:
An edited line replaces that line's generated notes.
Neighbouring generated lines are cut back so they don't overlap it. The edited line always wins.
Line breaks next to an edited line move to the middle of the gap. All other breaks are untouched.
The merged result goes to notes_final.json, with warnings recomputed so a line you fixed stops being reported. Warnings and the review read it when it exists.
When the lyrics change
Edits are stored with the lyrics they were made against. When the lyrics differ, each edited line is matched to its line in the new lyrics by text and order. Adding a line above an edited one moves the edit with its line. An edit whose line's words changed is set aside as stale rather than applied to different words: it's kept, reported as "N edited line(s) no longer match the lyrics", and can be discarded through the API.
API
GET /api/jobs/{id}/review now also returns each note's syllable and kind, whether each line is edited, any stale edits, and the chart's beat grid (BPM and GAP) for snapping. The fields that were already there are unchanged.
PUT /api/jobs/{id}/lines/{n} with {"notes": [...]} saves a line. Notes are validated: start ≥ 0, duration > 0, a known kind, non-empty text.
DELETE /api/jobs/{id}/lines/{n} reverts a line to the generated notes.
DELETE /api/jobs/{id}/stale/{n} discards a stale edit.
Each change rewrites the work chart and the packaged chart in place, keeping each one's headers and the BPM chosen at build time. Nothing is re-encoded, so a save is quick. Editing is refused with 409 while that song is queued or being rebuilt, since the build would overwrite the chart.
Other changes
Notes gain a kind (:, *, F, R, G), so golden, freestyle and rap notes reach the chart. Generated notes stay :, and older notes_sec.json files without the field still load.
grid now has two outputs, song.txt and notes_final.json. A song built before this change runs grid once more on its next rebuild. That's cheap.
Testing
All test lyrics are synthetic counting words.
tests/test_edits.py: merging, neighbours giving way, line breaks, edits following lines when lyrics are inserted above, stale edits, validation, the file being removed when no edits are left, and warnings.
grid stage: an edited line keeps its pitch and golden kind through two regenerations at different pitches. rechart swaps the notes and keeps the headers and tempo.
Web: saving rewrites both charts and clears the fixed line's warning; reverting; validation and out-of-range errors; 409 during a rebuild; discarding a stale edit.
Full suite passes, except test_version (needs the package installed, as CI does).
The design doc for the editor is kept outside the repo.
**Summary generated by LLM agent**
Phase 1 of turning the review viewer into an editor that replaces Yass. This is the part that keeps manual edits safe across every kind of rebuild, including REBUILD ALL FROM… and a rebuild from scratch. There's no editing UI yet; that's the next phase and builds on this API.
## How edits are kept
- **Separate file:** edits live in `work/<slug>/edits.json`. No stage lists it as an output, so no rebuild removes it. Deleting the song does.
- **Whole lines:** an edit is a whole lyric line as the user left it: every note's start, duration, pitch, syllable and kind. Editing one note saves the line.
- **Seconds, not beats:** a rebuild that picks a new BPM re-snaps the edit instead of breaking it.
On every run, `grid` lays the edits over the freshly generated notes before snapping to beats:
- An edited line replaces that line's generated notes.
- Neighbouring generated lines are cut back so they don't overlap it. The edited line always wins.
- Line breaks next to an edited line move to the middle of the gap. All other breaks are untouched.
The merged result goes to `notes_final.json`, with warnings recomputed so a line you fixed stops being reported. Warnings and the review read it when it exists.
## When the lyrics change
Edits are stored with the lyrics they were made against. When the lyrics differ, each edited line is matched to its line in the new lyrics by text and order. Adding a line above an edited one moves the edit with its line. An edit whose line's words changed is set aside as stale rather than applied to different words: it's kept, reported as "N edited line(s) no longer match the lyrics", and can be discarded through the API.
## API
- `GET /api/jobs/{id}/review` now also returns each note's syllable and `kind`, whether each line is `edited`, any `stale` edits, and the chart's beat `grid` (BPM and GAP) for snapping. The fields that were already there are unchanged.
- `PUT /api/jobs/{id}/lines/{n}` with `{"notes": [...]}` saves a line. Notes are validated: start ≥ 0, duration > 0, a known kind, non-empty text.
- `DELETE /api/jobs/{id}/lines/{n}` reverts a line to the generated notes.
- `DELETE /api/jobs/{id}/stale/{n}` discards a stale edit.
Each change rewrites the work chart and the packaged chart in place, keeping each one's headers and the BPM chosen at build time. Nothing is re-encoded, so a save is quick. Editing is refused with 409 while that song is queued or being rebuilt, since the build would overwrite the chart.
## Other changes
- Notes gain a `kind` (`:`, `*`, `F`, `R`, `G`), so golden, freestyle and rap notes reach the chart. Generated notes stay `:`, and older `notes_sec.json` files without the field still load.
- `grid` now has two outputs, `song.txt` and `notes_final.json`. A song built before this change runs `grid` once more on its next rebuild. That's cheap.
## Testing
All test lyrics are synthetic counting words.
- `tests/test_edits.py`: merging, neighbours giving way, line breaks, edits following lines when lyrics are inserted above, stale edits, validation, the file being removed when no edits are left, and warnings.
- `grid` stage: an edited line keeps its pitch and golden kind through two regenerations at different pitches. `rechart` swaps the notes and keeps the headers and tempo.
- Web: saving rewrites both charts and clears the fixed line's warning; reverting; validation and out-of-range errors; 409 during a rebuild; discarding a stale edit.
- Full suite passes, except `test_version` (needs the package installed, as CI does).
The design doc for the editor is kept outside the repo.
The groundwork for an in-app editor. Edits live in edits.json in the work
dir, which no stage produces, so no rebuild removes them. An edit is a
whole lyric line as the user left it, in seconds, so a rebuild with a new
BPM re-snaps it. grid lays the edits over the freshly generated notes
(neighbouring lines give way to an edited one), writes the result to
notes_final.json with warnings brought up to date, and charts that. When
the lyrics change, edits follow their lines by text and order; an edit
whose line's words changed is set aside as stale and reported, not applied.
Notes gain a kind, so golden, freestyle and rap notes can be charted. The
review endpoint returns the merged notes with their syllables, kinds and
edit state, plus the chart's beat grid. PUT/DELETE
/api/jobs/{id}/lines/{n} save and revert a line, DELETE
/api/jobs/{id}/stale/{n} discards a stale edit; each rewrites the work and
packaged charts in place with the tempo already chosen, so a save
re-encodes nothing.
branderson
approved these changes 2026-10-11 01:10:11 -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
Phase 1 of turning the review viewer into an editor that replaces Yass. This is the part that keeps manual edits safe across every kind of rebuild, including REBUILD ALL FROM… and a rebuild from scratch. There's no editing UI yet; that's the next phase and builds on this API.
How edits are kept
work/<slug>/edits.json. No stage lists it as an output, so no rebuild removes it. Deleting the song does.On every run,
gridlays the edits over the freshly generated notes before snapping to beats:The merged result goes to
notes_final.json, with warnings recomputed so a line you fixed stops being reported. Warnings and the review read it when it exists.When the lyrics change
Edits are stored with the lyrics they were made against. When the lyrics differ, each edited line is matched to its line in the new lyrics by text and order. Adding a line above an edited one moves the edit with its line. An edit whose line's words changed is set aside as stale rather than applied to different words: it's kept, reported as "N edited line(s) no longer match the lyrics", and can be discarded through the API.
API
GET /api/jobs/{id}/reviewnow also returns each note's syllable andkind, whether each line isedited, anystaleedits, and the chart's beatgrid(BPM and GAP) for snapping. The fields that were already there are unchanged.PUT /api/jobs/{id}/lines/{n}with{"notes": [...]}saves a line. Notes are validated: start ≥ 0, duration > 0, a known kind, non-empty text.DELETE /api/jobs/{id}/lines/{n}reverts a line to the generated notes.DELETE /api/jobs/{id}/stale/{n}discards a stale edit.Each change rewrites the work chart and the packaged chart in place, keeping each one's headers and the BPM chosen at build time. Nothing is re-encoded, so a save is quick. Editing is refused with 409 while that song is queued or being rebuilt, since the build would overwrite the chart.
Other changes
kind(:,*,F,R,G), so golden, freestyle and rap notes reach the chart. Generated notes stay:, and oldernotes_sec.jsonfiles without the field still load.gridnow has two outputs,song.txtandnotes_final.json. A song built before this change runsgridonce more on its next rebuild. That's cheap.Testing
All test lyrics are synthetic counting words.
tests/test_edits.py: merging, neighbours giving way, line breaks, edits following lines when lyrics are inserted above, stale edits, validation, the file being removed when no edits are left, and warnings.gridstage: an edited line keeps its pitch and golden kind through two regenerations at different pitches.rechartswaps the notes and keeps the headers and tempo.test_version(needs the package installed, as CI does).The design doc for the editor is kept outside the repo.
The groundwork for an in-app editor. Edits live in edits.json in the work dir, which no stage produces, so no rebuild removes them. An edit is a whole lyric line as the user left it, in seconds, so a rebuild with a new BPM re-snaps it. grid lays the edits over the freshly generated notes (neighbouring lines give way to an edited one), writes the result to notes_final.json with warnings brought up to date, and charts that. When the lyrics change, edits follow their lines by text and order; an edit whose line's words changed is set aside as stale and reported, not applied. Notes gain a kind, so golden, freestyle and rap notes can be charted. The review endpoint returns the merged notes with their syllables, kinds and edit state, plus the chart's beat grid. PUT/DELETE /api/jobs/{id}/lines/{n} save and revert a line, DELETE /api/jobs/{id}/stale/{n} discards a stale edit; each rewrites the work and packaged charts in place with the tempo already chosen, so a save re-encodes nothing.