diff --git a/static/js/cookbookDownload.js b/static/js/cookbookDownload.js index 4eb0038..4fe2d4b 100644 --- a/static/js/cookbookDownload.js +++ b/static/js/cookbookDownload.js @@ -555,18 +555,20 @@ export async function _runModelDownload(panel, model, backend, hostOverride) { body: JSON.stringify(payload), }); if (!res.ok) { - uiModule.showToast('Download failed: HTTP ' + res.status); + // Errors carry actionable text (e.g. "tmux is required …"); keep them up + // long enough to read, matching the serve path's duration (issue #1355). + uiModule.showToast('Download failed: HTTP ' + res.status, 9000); return; } const data = await res.json(); if (!data.ok) { - uiModule.showToast('Download failed: ' + (data.error || '')); + uiModule.showToast('Download failed: ' + (data.error || ''), 9000); return; } _addTask(data.session_id, shortName, 'download', payload); uiModule.showToast(`Downloading ${shortName}...`); } catch (e) { - uiModule.showToast('Download failed: ' + e.message); + uiModule.showToast('Download failed: ' + e.message, 9000); } } diff --git a/tests/test_cookbook_download_toast_duration.py b/tests/test_cookbook_download_toast_duration.py new file mode 100644 index 0000000..33afc52 --- /dev/null +++ b/tests/test_cookbook_download_toast_duration.py @@ -0,0 +1,27 @@ +"""Regression guard for issue #1355 — the Cookbook *download* error toast used +the default ~1.2s duration, so an actionable message like "tmux is required …" +vanished before it could be read. The serve path already used multi-second +durations; the download-failure toasts now match. + +cookbookDownload.js pulls in browser globals so it can't run under node; this +guards the durations at the source level. +""" +import re +from pathlib import Path + +SRC = Path(__file__).resolve().parent.parent / "static/js/cookbookDownload.js" +_MIN_MS = 5000 + + +def test_download_failure_toasts_stay_visible(): + # Each download-failure toast is a single line; assert each carries an + # explicit duration >= _MIN_MS so the actionable error stays readable. + lines = [ + ln for ln in SRC.read_text(encoding="utf-8").splitlines() + if "showToast(" in ln and "Download failed:" in ln + ] + assert lines, "expected at least one 'Download failed' showToast call" + for ln in lines: + m = re.search(r",\s*(\d{3,})\s*\)\s*;?\s*$", ln) + assert m, f"download-failure toast has no explicit duration: {ln.strip()}" + assert int(m.group(1)) >= _MIN_MS, f"duration too short to read: {ln.strip()}"