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.
renderLyricsReview was caching its job ID only in the done path, allowing
stale data to persist when switching between done/running/failed jobs.
Now clears the cache in the failed and not-done branches so a previously-
viewed done job always re-fetches when re-selected.
Regression test: test_lyrics_review_cache_clears_on_job_status_change
verifies both branches clear the cache key before rendering placeholders.
- Added classify_lyrics_match import to wire lyric classification into align stage
- Changed _assign_lines_to_sections signature to accept line_indices list instead of ref
- Modified _run to classify lyrics match, filter dropped lines, and write align_meta.json
- Sort segments by start time before processing for correct matching of non-chronological transcripts
- Added tests for dropped line detection and missing span flagging
- add test_rebuild_inherits_the_parents_cover: covers the cover_path
inheritance mechanism in the rebuild endpoint (Task 2's upload-retention
design relied on it, but nothing exercised it)
- tighten test_lineage_marker_survives_in_the_accessibility_tree to assert
the literal " rebuilt" screen-reader text, not just that rebuilt_from
is referenced somewhere in paintCard
- normalize submit's youtube_url/genre/language the same way rebuild does,
for symmetry, plus a pinning test
- pin acquire's stage outputs to [meta.json, audio.wav] in test_acquire.py,
since web.py's _require_source hardcodes those same filenames
Directly assert STAGE_LINE does not match the [rebuild] line, rather than
inferring it from job.stage, which ok_runner's later [acquire] running
line would overwrite either way.
Final whole-branch review fix wave (5 items):
- copyPath: fall back to execCommand("copy") via an off-screen textarea,
then to selecting the path text, since navigator.clipboard is undefined
outside secure contexts (i.e. this app's normal http:// deployment)
- refreshDetail: snapshot selectedId before fetching and discard the
response if selection changed while in flight, so a slow response can no
longer permanently mis-pair the review panel with the wrong job now that
polling stops when idle
- .st-queued: give the sidebar rail a colour that clears 3:1 against --panel
- now-bar: #now-label reflects running/idle state instead of always reading
"NOW RUNNING"
- empty-state copy: distinguish a failed job from "nothing to review", fix
the queue-empty messages to not ask the user to select something that
doesn't exist, and normalise voice to lowercase/unpunctuated
- package stage: cache off a package.done sentinel written only after every
sub-step succeeds, instead of the txt/mp3 files that land first. A partial
failure (e.g. ffprobe/PIL error after txt+mp3 are written) no longer looks
cached, so a retry actually finishes the folder instead of being skipped.
- package stage: a corrupt/non-image cover file now degrades to the existing
no-cover path (drop #COVER header) instead of raising UnidentifiedImageError.
- consolidate the three independent language-code tables (lyrics._LANG_MAP,
notes._PYPHEN, whisper_client._ISO) into languages.LANGUAGES so a language
known to one call site can't be silently mishandled by another (e.g.
Japanese reaching Whisper correctly but syllabifying with English rules).
- web.py: remove a job's uploads/<job_id> dir once it reaches a terminal
state; safe because acquire copies the upload into the per-song work dir
and package reads cover_path directly, both inside the synchronous
runner() call that precedes cleanup.
python:3.11-slim base relies on pip torch wheels bundling CUDA runtime libs,
with the host NVIDIA driver passed through via the nvidia container runtime
(compose GPU reservation); TORCH_HOME and XDG_CACHE_HOME point into the
mounted /data volume so Demucs/wav2vec2 weights survive container
recreation.
Docker was not installed in the implementation sandbox, so the image was
not built or run and remains untested. PyYAML was also unavailable, so
docker-compose.yml was not parse-checked programmatically — only hand
reviewed.