Three related webui improvements for songtrace's song review flow:
Song creation form: the video/YouTube source field is now visible up front instead of hidden behind an "Advanced" collapsible, and the lyrics textarea moved into a taller, narrower right-hand column.
Alignment pipeline: extra/never-sung reference lines are now detected via difflib opcode classification and excluded from the proportional time-distribution used for force-alignment (with a warning), instead of silently distorting timing for the lines that were sung. Sung-but-unreferenced audio (ad-libs, spoken asides) is similarly flagged and excluded from note generation.
Review UI: once a song finishes processing, the "Review" panel is now a visual, per-line SVG piano-roll — pitched notes rendered under each lyric line, low-confidence notes color-coded, dropped (never-sung) lines shown struck-through, and unmatched/missing audio spans shown as inline chronological markers. This replaces the old flat text warnings list. Backed by a new GET /api/jobs/{job_id}/review endpoint.
CLI behavior is unchanged; this only touches the webui and the pipeline stages that feed it.
Notable fix from final review
A whole-branch review caught a real bug that no single task's narrower review could see: classify_lyrics_match() was scoping "missing span" (unmatched audio) bounds to the entire containing vocal section rather than just the unmatched audio itself. Since vocal_sections() merges any segments within 6s of each other, a matched lyric line sharing a section with even a small ad-lib had all of its notes silently dropped from the piano roll. Fixed by scoping missing spans to the padded/merged bounds of just the unmatched segments (commit 2a7b53e), plus a related ordering bug in build_notes() that could corrupt line-initial spacing/break timing for a skipped row's own line. Both fixes have dedicated regression tests.
Known residual risks (please verify locally)
No browser available in the sandbox this was built in (no Chrome/Chromium/Playwright) — the new review UI's DOM structure and class assignment were verified by executing the real committed JS against real API responses via a Node.js DOM stub, but actual visual/CSS rendering was never painted by a real browser. Worth a manual look before merging.
docs/images/webui.png was not regenerated for the same reason. To update it: pip install -e '.[dev]' && playwright install chromium && python scripts/screenshot_webui.py (the script now includes a "Landslide" fixture demonstrating dropped lines, a missing span, and low-confidence notes).
Test plan
Full test suite passes: 253 passed, 1 skipped (PYTHONPATH=src .venv/bin/python -m pytest tests/ -q)
Implemented via subagent-driven development: fresh implementer per task, task-scoped review + fix loop after each, and a final whole-branch review with one fix round
Manual browser check of the new review UI (blocked in this sandbox — see above)
Regenerate docs/images/webui.png locally
**Summary generated by LLM agent**
## Summary
Three related webui improvements for songtrace's song review flow:
- **Song creation form**: the video/YouTube source field is now visible up front instead of hidden behind an "Advanced" collapsible, and the lyrics textarea moved into a taller, narrower right-hand column.
- **Alignment pipeline**: extra/never-sung reference lines are now detected via `difflib` opcode classification and excluded from the proportional time-distribution used for force-alignment (with a warning), instead of silently distorting timing for the lines that *were* sung. Sung-but-unreferenced audio (ad-libs, spoken asides) is similarly flagged and excluded from note generation.
- **Review UI**: once a song finishes processing, the "Review" panel is now a visual, per-line SVG piano-roll — pitched notes rendered under each lyric line, low-confidence notes color-coded, dropped (never-sung) lines shown struck-through, and unmatched/missing audio spans shown as inline chronological markers. This replaces the old flat text warnings list. Backed by a new `GET /api/jobs/{job_id}/review` endpoint.
CLI behavior is unchanged; this only touches the webui and the pipeline stages that feed it.
## Notable fix from final review
A whole-branch review caught a real bug that no single task's narrower review could see: `classify_lyrics_match()` was scoping "missing span" (unmatched audio) bounds to the *entire* containing vocal section rather than just the unmatched audio itself. Since `vocal_sections()` merges any segments within 6s of each other, a matched lyric line sharing a section with even a small ad-lib had **all of its notes silently dropped** from the piano roll. Fixed by scoping missing spans to the padded/merged bounds of just the unmatched segments (commit `2a7b53e`), plus a related ordering bug in `build_notes()` that could corrupt line-initial spacing/break timing for a skipped row's own line. Both fixes have dedicated regression tests.
## Known residual risks (please verify locally)
1. **No browser available in the sandbox this was built in** (no Chrome/Chromium/Playwright) — the new review UI's DOM structure and class assignment were verified by executing the real committed JS against real API responses via a Node.js DOM stub, but actual visual/CSS rendering was never painted by a real browser. Worth a manual look before merging.
2. **`docs/images/webui.png` was not regenerated** for the same reason. To update it: `pip install -e '.[dev]' && playwright install chromium && python scripts/screenshot_webui.py` (the script now includes a "Landslide" fixture demonstrating dropped lines, a missing span, and low-confidence notes).
## Test plan
- [x] Full test suite passes: 253 passed, 1 skipped (`PYTHONPATH=src .venv/bin/python -m pytest tests/ -q`)
- [x] Implemented via subagent-driven development: fresh implementer per task, task-scoped review + fix loop after each, and a final whole-branch review with one fix round
- [ ] Manual browser check of the new review UI (blocked in this sandbox — see above)
- [ ] Regenerate `docs/images/webui.png` locally
Both residual risks noted in the PR description are now resolved:
Manual browser check of the review UI: a headless-Chromium run confirms the piano-roll note strips and the ♩ unmatched singing, 2:10–2:18 missing-span marker render correctly with real CSS.
Regenerate docs/images/webui.png locally: done, commit 153c83b (already pushed) — shows the new review panel for the Landslide fixture.
Full test suite still green (253 passed, 1 skipped).
Both residual risks noted in the PR description are now resolved:
- **Manual browser check of the review UI**: a headless-Chromium run confirms the piano-roll note strips and the `♩ unmatched singing, 2:10–2:18` missing-span marker render correctly with real CSS.
- **Regenerate `docs/images/webui.png` locally**: done, commit 153c83b (already pushed) — shows the new review panel for the Landslide fixture.
Full test suite still green (253 passed, 1 skipped).
Also fix a secondary ripple in build_notes(): a skipped row (inside a
missing span) no longer advances prev_line/prev_end, so it can't corrupt
line-initial spacing or break timing for its own line's later words.
The review panel used to show a full line of packed text with one
undifferentiated strip of notes below it, so it was impossible to tell
which notes belonged to which word. The forced-alignment stage already
produces per-word timestamps in alignment.json, but they were never
surfaced past the pipeline.
/api/jobs/{id}/review now includes each line's per-word timing, and the
frontend spaces words out by their real sung timing (karaoke-style)
instead of normal text flow, with the note strip sharing that same time
axis so each note lands directly under its word. Alternating lane
backgrounds make the word/note pairing unambiguous. Dropped lines and
jobs without alignment data keep the old plain-text rendering.
Replace arbitrary demo pitches with the actual melody transcribed from
Gilbert DeBenedetti's public-domain "Level 4, Flowing" arrangement (E
minor, 6/8), converted to UltraStar semitones, with one note per word
across both demo lines.
.review-lane spans the full timeline box height (top:0; bottom:0) to
group each word with its note visually, but as a positioned element it
painted after the unpositioned .review-roll SVG, so the lane's
background covered the note bars for every other word. Give .review-roll
position: relative so DOM order (lanes, then notes) governs paint order
instead.
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
Summary
Three related webui improvements for songtrace's song review flow:
difflibopcode classification and excluded from the proportional time-distribution used for force-alignment (with a warning), instead of silently distorting timing for the lines that were sung. Sung-but-unreferenced audio (ad-libs, spoken asides) is similarly flagged and excluded from note generation.GET /api/jobs/{job_id}/reviewendpoint.CLI behavior is unchanged; this only touches the webui and the pipeline stages that feed it.
Notable fix from final review
A whole-branch review caught a real bug that no single task's narrower review could see:
classify_lyrics_match()was scoping "missing span" (unmatched audio) bounds to the entire containing vocal section rather than just the unmatched audio itself. Sincevocal_sections()merges any segments within 6s of each other, a matched lyric line sharing a section with even a small ad-lib had all of its notes silently dropped from the piano roll. Fixed by scoping missing spans to the padded/merged bounds of just the unmatched segments (commit2a7b53e), plus a related ordering bug inbuild_notes()that could corrupt line-initial spacing/break timing for a skipped row's own line. Both fixes have dedicated regression tests.Known residual risks (please verify locally)
docs/images/webui.pngwas not regenerated for the same reason. To update it:pip install -e '.[dev]' && playwright install chromium && python scripts/screenshot_webui.py(the script now includes a "Landslide" fixture demonstrating dropped lines, a missing span, and low-confidence notes).Test plan
PYTHONPATH=src .venv/bin/python -m pytest tests/ -q)docs/images/webui.pnglocallyBoth residual risks noted in the PR description are now resolved:
♩ unmatched singing, 2:10–2:18missing-span marker render correctly with real CSS.docs/images/webui.pnglocally: done, commit153c83b(already pushed) — shows the new review panel for the Landslide fixture.Full test suite still green (253 passed, 1 skipped).
153c83bd99toaa21d60d5eThe review panel used to show a full line of packed text with one undifferentiated strip of notes below it, so it was impossible to tell which notes belonged to which word. The forced-alignment stage already produces per-word timestamps in alignment.json, but they were never surfaced past the pipeline. /api/jobs/{id}/review now includes each line's per-word timing, and the frontend spaces words out by their real sung timing (karaoke-style) instead of normal text flow, with the note strip sharing that same time axis so each note lands directly under its word. Alternating lane backgrounds make the word/note pairing unambiguous. Dropped lines and jobs without alignment data keep the old plain-text rendering.