fix: clip queue resolves row index by filename, not stored _idx

clipQueue entries snapshotted fileIdx (= _idx) when the queue was armed,
but _idx is reassigned positionally on every load(), and pollStatus()
reloads the list whenever a recording starts, stops, or rolls over to a
new split file. With the newest-first sort a rollover shifts every index
by one, so an armed queue then pre-filled the wrong row's cut fields and
"Open in file" (O) opened the wrong row while streaming the clip's file
into it — during exactly the workflow of reviewing yesterday while
today's recording runs.

Store only the filename in queue entries and resolve the row index at
use time (fileIdxByName); if the file left the listing, Open in file
does nothing instead of acting on a wrong row.

Also drop seekToSection's sectionIdx parameter: the only remaining
caller passed null, leaving the announce branch dead since chips moved
to clip playback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 00:00:51 +02:00
co-authored by Claude Fable 5
parent 89c95a70a2
commit 24569efe20
2 changed files with 15 additions and 11 deletions
+14 -10
View File
@@ -235,6 +235,12 @@ function setCutFields(idx, startSec, endSec) {
if (endEl && endSec != null) endEl.value = fmtDur(endSec);
}
// Row index for a filename, resolved at use time: _idx values are reassigned
// positionally on every load(), and pollStatus() reloads the list whenever a
// recording starts/stops/rolls over — a stored index would point one row off
// after that. Null when the file is gone from the current listing.
const fileIdxByName = name => allFiles.find(f => f.name === name)?._idx ?? null;
// idx -> filename, for live-status polling
const recMap = new Map();
// idx -> [{start,end}], populated after analysis
@@ -325,7 +331,7 @@ function parseTime(s) {
return parts[0];
}
function seekToSection(idx, filename, startSec, endSec, sectionIdx) {
function seekToSection(idx, filename, startSec, endSec) {
const pbtn = document.getElementById('pbtn-'+idx);
if (pbtn.getAttribute('aria-expanded') !== 'true') togglePlayer(idx, filename);
activePlayerIdx = idx;
@@ -336,10 +342,6 @@ function seekToSection(idx, filename, startSec, endSec, sectionIdx) {
if (audio.readyState >= 1) doSeek();
else audio.addEventListener('loadedmetadata', doSeek, {once: true});
setCutFields(idx, startSec, endSec);
if (sectionIdx != null) {
const total = (sectionMap.get(idx) || []).length;
announce(`Section ${sectionIdx + 1} of ${total}: ${fmtDur(startSec)} to ${fmtDur(endSec)}`);
}
}
// --- clip player -----------------------------------------------------------
@@ -390,7 +392,8 @@ function playClip(i, byScore) {
label.title = `${c.filename} @ ${fmtDur(c.start)}–${fmtDur(c.end)}`;
document.getElementById('clip-bar').hidden = false;
document.body.classList.add('clip-open');
setCutFields(c.fileIdx, c.start, c.end);
const rowIdx = fileIdxByName(c.filename);
if (rowIdx != null) setCutFields(rowIdx, c.start, c.end);
announce(text);
}
@@ -409,7 +412,7 @@ function playFileSection(idx, filename, si) {
const secs = sectionMap.get(idx) || [];
const f = allFiles.find(f => f._idx === idx);
const epoch = f ? fileStartEpoch(f.date) : null;
clipQueue = secs.map(s => ({fileIdx: idx, filename, start: s.start, end: s.end, score: s.score,
clipQueue = secs.map(s => ({filename, start: s.start, end: s.end, score: s.score,
absStart: epoch != null ? epoch + s.start : null}));
scoreCursor = -1; // explicit jump: next U/I re-anchors here
playClip(si);
@@ -468,15 +471,17 @@ document.getElementById('clip-audio').addEventListener('ended', () => {
function openClipInFile() {
const c = clipQueue[clipCursor];
if (!c) return;
const idx = fileIdxByName(c.filename);
if (idx == null) return;
document.getElementById('clip-audio').pause();
const row = document.getElementById('row-' + c.fileIdx);
const row = document.getElementById('row-' + idx);
if (row) {
const tbl = row.closest('table');
if (tbl && tbl.hidden) // file's day is collapsed: expand it
document.getElementById('daytgl-' + tbl.id.slice('daytbl-'.length))?.click();
row.scrollIntoView({block: 'center'});
}
seekToSection(c.fileIdx, c.filename, c.start, c.end, null);
seekToSection(idx, c.filename, c.start, c.end);
}
document.getElementById('clip-context').addEventListener('click', openClipInFile);
@@ -1024,7 +1029,6 @@ async function dayHighlights(dayId, analyzableFiles) {
positioned.forEach(({ f, data, fileStart }) => {
(data.sections || []).forEach(s => {
dayActiveSections.push({
fileIdx: f._idx,
filename: f.name,
start: s.start,
end: s.end,