Takosaga commited on
Commit
e0f9bcc
Β·
1 Parent(s): 67fd4cd

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