ACloudCenter Claude Fable 5 commited on
Commit
fb5cad2
·
1 Parent(s): 9d60340

Say when a take has been cleared instead of failing the download

Browse files

Takes live in memory, so a Space redeploy or restart drops them and the
download links 404 — surfacing as the browser's opaque 'File wasn't
available on site'. Download clicks now probe the stored take with a
one-byte Range request first and, if it is gone, explain what happened
and why in the app rather than handing the user a dead link. Playback
load failures report the same thing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

Files changed (1) hide show
  1. static/app.js +42 -2
static/app.js CHANGED
@@ -1533,14 +1533,54 @@ function noteExportStarted(kind) {
1533
  noteExportStarted.timer = setTimeout(() => { el.polishStatus.hidden = true; }, 20000);
1534
  }
1535
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1536
  [["downloadBtn", "WAV"], ["stageDownloadBtn", "WAV"],
1537
  ["downloadMp3Btn", "MP3"], ["stageDownloadMp3Btn", "MP3"]].forEach(([id, kind]) => {
1538
- el[id].addEventListener("click", () => {
1539
- // The stored WAV is served instantly; everything else is rendered on demand.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1540
  if (kind === "MP3" || !polishIsDefault()) noteExportStarted(kind);
 
 
1541
  });
1542
  });
1543
 
 
 
 
 
 
1544
  function polishTouched() {
1545
  ensureAudioGraph();
1546
  if (polish.ctx && polish.ctx.state === "suspended") polish.ctx.resume();
 
1533
  noteExportStarted.timer = setTimeout(() => { el.polishStatus.hidden = true; }, 20000);
1534
  }
1535
 
1536
+ const TAKE_GONE_MESSAGE =
1537
+ "This take is no longer on the server — it is held in memory, so a Space restart or " +
1538
+ "redeploy clears it. Generate a new one; download it before deploying next time.";
1539
+
1540
+ function showTakeGone() {
1541
+ el.polishStatus.hidden = false;
1542
+ el.polishStatus.textContent = TAKE_GONE_MESSAGE;
1543
+ el.dockEmpty.hidden = false;
1544
+ el.dockEmpty.textContent = TAKE_GONE_MESSAGE;
1545
+ setStatus("error", "The finished audio is no longer available on the server.");
1546
+ }
1547
+
1548
+ // A dead link would otherwise surface as the browser's opaque "File wasn't
1549
+ // available on site". Probe the stored take (one byte) before letting the
1550
+ // download proceed, and say plainly when it has been cleared.
1551
  [["downloadBtn", "WAV"], ["stageDownloadBtn", "WAV"],
1552
  ["downloadMp3Btn", "MP3"], ["stageDownloadMp3Btn", "MP3"]].forEach(([id, kind]) => {
1553
+ el[id].addEventListener("click", async (event) => {
1554
+ const anchor = el[id];
1555
+ if (anchor.dataset.verified === "1") {
1556
+ anchor.dataset.verified = "";
1557
+ return; // second pass: this is the real download
1558
+ }
1559
+ if (!polish.audioId) return;
1560
+ event.preventDefault();
1561
+ let alive = false;
1562
+ try {
1563
+ // Probe the raw take, never the export URL — that would start a render.
1564
+ const probe = await fetch(`/api/audio/${polish.audioId}`, { headers: { Range: "bytes=0-0" } });
1565
+ alive = probe.ok || probe.status === 206;
1566
+ } catch {
1567
+ alive = false;
1568
+ }
1569
+ if (!alive) {
1570
+ showTakeGone();
1571
+ return;
1572
+ }
1573
  if (kind === "MP3" || !polishIsDefault()) noteExportStarted(kind);
1574
+ anchor.dataset.verified = "1";
1575
+ anchor.click();
1576
  });
1577
  });
1578
 
1579
+ // Same story if playback itself can't load the take.
1580
+ el.resultAudio.addEventListener("error", () => {
1581
+ if (el.resultAudio.getAttribute("src")) showTakeGone();
1582
+ });
1583
+
1584
  function polishTouched() {
1585
  ensureAudioGraph();
1586
  if (polish.ctx && polish.ctx.state === "suspended") polish.ctx.resume();