ソースを参照

Disable export while sample files are missing

Choosing a folder only to get an error was easy to miss. Disable both
export buttons when any sample file is missing and say why in the
footer and tooltip; the store also refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude 1 日 前
親
コミット
a40be02388

+ 7 - 3
frontend/src/App.svelte

@@ -33,6 +33,10 @@
     });
   }
 
+  // Export would fail on a missing sample file, so it is not offered at all.
+  const exportBlocked = $derived((st?.missingCount ?? 0) > 0);
+  const exportBlockedWhy = $derived(exportBlocked ? "Export is disabled while sample files are missing. Replace them, or put the files back and load the project again." : undefined);
+
   const padCounts = $derived(perBank((s) => (pads[s]?.file ?? "") !== ""));
   const seqCounts = $derived(perBank((s) => seqBySlot.has(s)));
 
@@ -120,8 +124,8 @@
     <div class="actions">
       <button type="button" class="btn" onclick={newProject}>New</button>
       <button type="button" class="btn" onclick={loadProject}>Load</button>
-      <button type="button" class="btn" onclick={exportZip}>Export zip</button>
-      <button type="button" class="btn primary" onclick={exportFolder}>Export to folder</button>
+      <button type="button" class="btn" disabled={exportBlocked} title={exportBlockedWhy} onclick={exportZip}>Export zip</button>
+      <button type="button" class="btn primary" disabled={exportBlocked} title={exportBlockedWhy} onclick={exportFolder}>Export to folder</button>
     </div>
     <button type="button" class="about-link" onclick={() => (ui.aboutOpen = true)}>About</button>
   </header>
@@ -188,7 +192,7 @@
       <span>{st.sampleCount} of {st.limits.maxSamples} samples</span>
       <span>{st.sequenceCount} of {st.limits.maxSequences} sequences</span>
       <span>WAV files only</span>
-      {#if st.missingCount}<span class="missing">{st.missingCount} sample {st.missingCount === 1 ? "file" : "files"} missing</span>{/if}
+      {#if st.missingCount}<span class="missing">{st.missingCount} sample {st.missingCount === 1 ? "file" : "files"} missing: export is disabled</span>{/if}
       {#if st.dirty}<span class="dirty">Unsaved changes: export to keep them</span>{/if}
       {#if app.notice}<span class="notice" title={app.notice}>{app.notice}</span>{/if}
     </footer>

+ 1 - 1
frontend/src/lib/PadPanel.svelte

@@ -51,7 +51,7 @@
         <div class="missing" role="status">
           <strong>Sample file not found.</strong>
           <span class="mono">{pad.source}</span>
-          <span>Replace it, or put the file back and load the project again. Export is blocked until then.</span>
+          <span>Replace it, or put the file back and load the project again. Export is disabled until then.</span>
         </div>
       {/if}
       <div class="row">

+ 1 - 0
frontend/src/lib/store.svelte.ts

@@ -147,6 +147,7 @@ async function exportWith(
   choose: () => Promise<string>,
   write: (target: string, overwrite: boolean) => Promise<ExportResult>,
 ): Promise<void> {
+  if (app.state?.missingCount) return; // the buttons are disabled too
   try {
     const target = await choose();
     if (!target) return; // dialog cancelled

+ 4 - 2
pull-requests/m8-load-guard-rails/m8-load-and-guard-rails.md

@@ -10,12 +10,14 @@ Load itself, the unsaved-changes prompt on New and Load, the overwrite confirmat
   - The grid shows `missing` (in red) on such pads.
   - The pad panel shows the expected path and says to replace the sample or put the file back and load again. Play is disabled.
   - The footer counts missing files.
-  - Export refuses and lists every missing file, as since M3, and writes nothing.
+  - **Export zip and Export to folder are disabled** while any sample file is missing, and the footer says so ("1 sample file missing: export is disabled"). The buttons' tooltip gives the reason too. Go still refuses such an export and lists every missing file, as since M3, if it's ever asked.
   - Replacing the sample clears the warning.
 - **Closing the window with unsaved changes.** A Go hook stops the close and sends a `close` event. The app asks "Close without exporting?"; Close discards the changes and quits, Cancel stays. Without unsaved changes the window closes straight away.
 - **Device checkpoint B checklist** (`docs/checkpoint-b.md`, linked from the build plan). Five projects (MpckitB1–B5), each made from DemoFile in the app: no changes, moving a pad with notes, copying across banks and swapping pads with notes, removing the current sequence and adding one in bank B, and shortening a sequence with notes. Each lists what to listen for, plus an empty results table. It also covers open question 7 (`currentSequence`) and B5 (notes past a new end).
 - Go service: `CloseWindow` for the frontend. `Dirty` and `SetCloser` are for `main.go` only and kept out of the bindings.
 
+**Change after the first laptop run** (bajdev, 2026-11-11: checks 1–3 passed): disable the export buttons while a sample is missing. Before, a user could choose a folder and miss the error text.
+
 ## Testing
 
 On the server: `go vet` and `go test ./...` pass. New tests:
@@ -25,6 +27,6 @@ On the server: `go vet` and `go test ./...` pass. New tests:
 The frontend is untested here (no Node). **On the laptop:**
 
 1. `npm run check` in `frontend`: 0 errors, 0 warnings.
-2. **Missing file:** copy `DemoFile.xpj` and its `DemoFile_[ProjectData]` folder somewhere, delete `80s-Snr-Dreamer SA.wav` from the copy, and Load it. A02 shows `missing`, the footer says 1 sample file missing, A02's panel shows the path, and Play is disabled. Export reports the missing file. Replace A02 with any WAV: the warning goes and export works.
+2. **Missing file:** copy `DemoFile.xpj` and its `DemoFile_[ProjectData]` folder somewhere, delete `80s-Snr-Dreamer SA.wav` from the copy, and Load it. A02 shows `missing`, A02's panel shows the path, and Play is disabled. Both export buttons are disabled, and the footer says "1 sample file missing: export is disabled". Replace A02 with any WAV: the warning goes and both export buttons work again.
 3. **Close:** make any change and close the window. The app asks; Cancel stays open. Close again and choose Close: the app quits. With no changes (or right after exporting) the window closes without asking.
 4. **Checkpoint B** (M8 done-when): build the five projects in `docs/checkpoint-b.md`, try them on the device, and send me the results. I'll record them in the format doc and build plan.