Spaces:
Build error
Build error
docs: add export fix design spec (single-click download, flat folder, meaningful filenames)
Browse files
docs/superpowers/specs/2026-06-13-export-fix-design.md
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Export Fix β Single-Click Download, Flat Folder, Meaningful Filenames
|
| 2 |
+
|
| 3 |
+
**Date:** 2026-06-13
|
| 4 |
+
**Status:** Approved
|
| 5 |
+
|
| 6 |
+
## Problem Statement
|
| 7 |
+
|
| 8 |
+
Three issues with the CSV export feature:
|
| 9 |
+
|
| 10 |
+
1. **Two-click download bug:** The export handler is a generator that yields progress first (with `None` path), then yields the zip path on a second yield. Gradio's DownloadButton only triggers a browser download when it receives a file path β the first yield sets its value to `None`, so the user must click again.
|
| 11 |
+
|
| 12 |
+
2. **Meaningless file names:** Media files are named `audio_0.wav`, `image_0.png` inside subfolders, giving no context about which scenario or language they belong to.
|
| 13 |
+
|
| 14 |
+
3. **Export status label clutter:** A `gr.Label("Export Status")` widget is shown under the download button, adding noise to an already clean UI.
|
| 15 |
+
|
| 16 |
+
## Design
|
| 17 |
+
|
| 18 |
+
### 1. Fix Two-Click Download
|
| 19 |
+
|
| 20 |
+
**File:** `frontend/ui/widgets.py`
|
| 21 |
+
**Change:** Convert `_handle_export_csv_event` from a generator to a sync function.
|
| 22 |
+
|
| 23 |
+
The current handler yields twice:
|
| 24 |
+
```python
|
| 25 |
+
# Current (broken): two yields, first with None path
|
| 26 |
+
yield (progress_html_0, None) # Gradio sets DownloadButton value β None
|
| 27 |
+
yield (progress_html_1, zip_path) # User must click again to trigger download
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
The new handler returns once:
|
| 31 |
+
```python
|
| 32 |
+
# New (fixed): single return
|
| 33 |
+
return (progress_html_final, zip_path) # Gradio sets path β triggers download immediately
|
| 34 |
+
```
|
| 35 |
+
|
| 36 |
+
Since the export is fast (<1 second), a progress bar update is unnecessary. The function writes the zip, then returns `(progress_html, zip_path)` in one shot. Gradio's DownloadButton receives the path and triggers the browser save dialog on the first click.
|
| 37 |
+
|
| 38 |
+
### 2. Flat Folder + Meaningful Filenames
|
| 39 |
+
|
| 40 |
+
**File:** `export/csv_export.py`
|
| 41 |
+
**Changes:**
|
| 42 |
+
- Remove `audio_dir` and `images_dir` subfolder creation
|
| 43 |
+
- Rename media files to `{scenario_slug}_{cefr_level}_{lang_abbr}_{card_index}.{ext}`
|
| 44 |
+
- Example: `ordering_coffee_A2_LV_0.wav`, `ordering_coffee_A2_LV_1.png`
|
| 45 |
+
- Update CSV `audio_filename` and `image_filename` columns to contain just the filename (not a path like `audio/audio_0.wav`)
|
| 46 |
+
|
| 47 |
+
**Before:**
|
| 48 |
+
```
|
| 49 |
+
ordering_coffee_A2_LV/
|
| 50 |
+
cards.csv # audio_filename="audio/audio_0.wav"
|
| 51 |
+
audio/
|
| 52 |
+
audio_0.wav
|
| 53 |
+
images/
|
| 54 |
+
image_0.png
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
**After:**
|
| 58 |
+
```
|
| 59 |
+
ordering_coffee_A2_LV/
|
| 60 |
+
cards.csv # audio_filename="ordering_coffee_A2_LV_0.wav"
|
| 61 |
+
ordering_coffee_A2_LV_0.wav
|
| 62 |
+
ordering_coffee_A2_LV_1.png
|
| 63 |
+
```
|
| 64 |
+
|
| 65 |
+
Language abbreviations already have no periods (LV, ES, FR, DE, PL, IT, PT, FI) β confirmed from `_LANGUAGE_ABBREVS` mapping.
|
| 66 |
+
|
| 67 |
+
### 3. Remove Export Status Label
|
| 68 |
+
|
| 69 |
+
**File:** `frontend/ui/widgets.py`
|
| 70 |
+
**Changes:**
|
| 71 |
+
- Remove `export_status = gr.Label(...)` widget from UI layout
|
| 72 |
+
- Remove `export_status` from all tuple returns in `_reset_to_idle()` and `_on_media_generation_complete()`
|
| 73 |
+
- Remove `export_status` from all `.then()` / `.change()` output lists in event wiring
|
| 74 |
+
|
| 75 |
+
## Files Modified
|
| 76 |
+
|
| 77 |
+
| File | Lines Changed | Type |
|
| 78 |
+
|---|---|---|
|
| 79 |
+
| `export/csv_export.py` | ~15 lines | Logic change (rename + flatten) |
|
| 80 |
+
| `frontend/ui/widgets.py` | ~20 lines | Event handler rewrite + widget removal |
|
| 81 |
+
|
| 82 |
+
No new files. No dependencies added. No API changes to existing modules.
|
| 83 |
+
|
| 84 |
+
## Testing Impact
|
| 85 |
+
|
| 86 |
+
- `tests/csv_export_test.py` β needs updates for new file naming and flat folder structure assertions
|
| 87 |
+
- `tests/widgets_test.py` β may need updates if `_reset_to_idle()` or `_on_media_generation_complete()` output counts change
|