From 2509a596c1caff6e4d1612d6002ad39b080919cd Mon Sep 17 00:00:00 2001 From: davegilligan Date: Sun, 24 May 2026 08:23:58 +0200 Subject: [PATCH] Fix file picker double-open across all remaining tools Same defensive guard as legal-analysis + summarize, now applied to the upload-zone and audio-zone handlers in tools.js. Affects redact, timeline, and transcribe (which all share these zones via tools.js's setupUpload / setupAudioUpload). Stops native label-for clicks and the input's own click from bubbling into the zone handler that would otherwise programmatically re-open the picker. Co-Authored-By: Claude Sonnet 4.6 --- assets/js/tools.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/assets/js/tools.js b/assets/js/tools.js index 468ea7c..1bb2b17 100644 --- a/assets/js/tools.js +++ b/assets/js/tools.js @@ -1180,9 +1180,16 @@ function setupUpload() { if (e.dataTransfer?.files?.length) handleFiles(e.dataTransfer.files); }); + // Stop label-for and the input itself from bubbling into the zone click + // handler — otherwise the picker opens twice (native + programmatic). + const _uploadLabel = els.uploadZone.querySelector('label[for="' + els.uploadInput.id + '"]'); + if (_uploadLabel) _uploadLabel.addEventListener('click', (e) => e.stopPropagation()); + els.uploadInput.addEventListener('click', (e) => e.stopPropagation()); els.uploadZone.addEventListener('click', (e) => { if (e.target === els.uploadClear || els.uploadClear?.contains(e.target)) return; - if (e.target.tagName === 'LABEL') return; + if (e.target === els.uploadInput) return; + const lbl = e.target.closest && e.target.closest('label'); + if (lbl && lbl.getAttribute('for') === els.uploadInput.id) return; els.uploadInput.click(); }); @@ -2268,11 +2275,17 @@ function setupAudio() { if (e.dataTransfer?.files?.length) handleAudioFiles(e.dataTransfer.files); }); + // Stop label-for and the input itself from bubbling into the zone click + // handler — otherwise the picker opens twice (native + programmatic). + const _audioLabel = els.audioZone.querySelector('label[for="' + els.audioInput.id + '"]'); + if (_audioLabel) _audioLabel.addEventListener('click', (e) => e.stopPropagation()); + els.audioInput.addEventListener('click', (e) => e.stopPropagation()); els.audioZone.addEventListener('click', (e) => { if (e.target === els.audioClear || els.audioClear?.contains(e.target)) return; if (e.target === els.audioInput) return; - if (e.target.tagName === 'LABEL') return; - if (e.target.closest('#audioFileInfo') && e.target.tagName !== 'LABEL') return; + const lbl = e.target.closest && e.target.closest('label'); + if (lbl && lbl.getAttribute('for') === els.audioInput.id) return; + if (e.target.closest('#audioFileInfo')) return; els.audioInput.click(); });