# Hit review and progress streaming Last updated: 2026-05-12 ## Purpose This pass moves the app closer to a review workstation by making detected hits individually inspectable and by replacing frontend-only polling with a server-sent-events progress channel. ## Implemented behavior | Area | Implementation | Files | |---|---|---| | Review hit artifacts | Every accepted detected hit is written as an individual WAV under `review/hits/`. | `pipeline_runner.py` | | Manifest hit rows | `manifest.json` now includes a top-level `hits` array with onset, duration, label, cluster, representative flag, and relative file path. | `pipeline_runner.py` | | Hit URLs | API serialization adds direct download/playback URLs to every hit row. | `app.py` | | Waveform selection | Clicking the waveform selects the nearest detected onset marker. | `web/app.js` | | Hit audition | Clicking a hit row or waveform marker loads that hit into the selected-hit audio player. | `web/index.html`, `web/app.js` | | Sample audition | Representative sample rows now have explicit Audition buttons and a dedicated selected-sample player. | `web/index.html`, `web/app.js` | | SSE progress | `GET /api/jobs/{job_id}/events` streams job snapshots whenever state changes. | `app.py`, `web/app.js` | | Poll fallback | The frontend falls back to polling if `EventSource` is unavailable or errors. | `web/app.js` | | Artifact serving hardening | File downloads now use `Path.relative_to()` against the resolved run output directory. | `app.py` | ## Manifest shape additions Completed results now include: ```json { "hits": [ { "index": 0, "label": "kick", "cluster_id": 3, "cluster_label": "kick_0", "is_representative": true, "onset_sec": 0.002993, "duration_ms": 255.0, "rms_energy": 0.141768, "spectral_centroid_hz": 773.4, "file": "review/hits/hit_00000_kick.wav" } ] } ``` API responses add `url` to each hit row, for example: ```json { "file": "review/hits/hit_00000_kick.wav", "url": "/api/jobs//files/review/hits/hit_00000_kick.wav" } ``` The `overview.onsets` entries now also carry `index` and `duration_sec`, allowing the waveform to map markers back to review hit rows. ## Streaming endpoint `GET /api/jobs/{job_id}/events` returns `text/event-stream`. Each emitted event has type `job` and contains the same serialized shape as `GET /api/jobs/{job_id}`: ```text event: job data: {"id":"...","status":"running",...} ``` The stream ends after `complete` or `error`. Completed historical jobs stream one final event and then close. ## Current limitations - Hit review is read-only. It does not yet support delete/shift/relabel actions. - Every accepted hit is exported as a WAV. This is correct for review UX, but large files with thousands of hits may produce many small artifacts. - SSE streams job snapshots, not fine-grained internal Demucs progress. - The waveform is an overview canvas, not an editable detailed waveform yet. ## Next editor step Add an edit state layer on top of the hit manifest: 1. Mark hit deleted/restored. 2. Shift onset and duration bounds. 3. Reassign cluster label. 4. Merge/split clusters. 5. Re-render/repack from edited manifest without rerunning Demucs or onset detection.