VibecoderMcSwaggins commited on
Commit
f80ee45
Β·
1 Parent(s): 5ac4ed0

fix(docs): update technical debt documentation and add root cause analysis for NiiVue loading issue

Browse files

- Revised the TECHNICAL_DEBT.md to reflect the current status of the NiiVue/WebGL integration, marking it as a P0 BLOCKER due to issues on HuggingFace Spaces.
- Added detailed sections outlining the critical issue with NiiVue not loading, including root causes and proposed solutions.
- Introduced new specifications for the Gradio Custom Component approach to resolve the loading issue, emphasizing the need for a structured solution.
- Documented the findings from the recent audits and analyses, providing a clear path forward for developers addressing the integration challenges.

These updates aim to enhance clarity and provide actionable insights for future development efforts.

docs/TECHNICAL_DEBT.md CHANGED
@@ -1,21 +1,55 @@
1
  # Technical Debt and Known Issues
2
 
3
- > **Last Audit**: December 2025 (Revision 5)
4
  > **Auditor**: Claude Code + External Senior Review
5
- > **Status**: Ironclad / Production-Ready (Google DeepMind level)
6
 
7
  ## Summary
8
 
9
- Full architectural review completed. All critical and major technical debt items have been **resolved** via TDD.
10
 
11
  | Severity | Count | Description | Status |
12
  |----------|-------|-------------|--------|
 
13
  | P2 (Medium) | 0 | Temp dir leak, silent empty dataset, brittle git dep | **All Fixed** |
14
  | P3 (Low) | 0 | SSRF vector, float64 memory, base64 overhead | **All Fixed** |
15
  | P3 (Low) | 1 | Type ignores | **Acceptable** |
16
 
17
  ---
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  ## Resolved Issues (Fixed in `fix/technical-debt`)
20
 
21
  ### βœ… P2: Silent Empty Dataset on Missing Data Directory
@@ -58,4 +92,6 @@ See: `docs/specs/19-perf-base64-to-file-urls.md`
58
 
59
  ## Conclusion
60
 
61
- The codebase has been hardened to a high standard of quality ("Ironclad"). All failure modes identified in the audit are now covered by regression tests and fixed in the implementation.
 
 
 
1
  # Technical Debt and Known Issues
2
 
3
+ > **Last Audit**: December 2025 (Revision 6)
4
  > **Auditor**: Claude Code + External Senior Review
5
+ > **Status**: P0 BLOCKER - NiiVue/WebGL integration broken on HF Spaces
6
 
7
  ## Summary
8
 
9
+ **CRITICAL ISSUE**: The NiiVue 3D viewer does not work on HuggingFace Spaces due to Gradio architecture limitations. See Issue #24.
10
 
11
  | Severity | Count | Description | Status |
12
  |----------|-------|-------------|--------|
13
+ | **P0 (Critical)** | 1 | NiiVue/WebGL on HF Spaces | **BLOCKED** |
14
  | P2 (Medium) | 0 | Temp dir leak, silent empty dataset, brittle git dep | **All Fixed** |
15
  | P3 (Low) | 0 | SSRF vector, float64 memory, base64 overhead | **All Fixed** |
16
  | P3 (Low) | 1 | Type ignores | **Acceptable** |
17
 
18
  ---
19
 
20
+ ## P0 BLOCKER: NiiVue/WebGL on HuggingFace Spaces (Issue #24)
21
+
22
+ ### Problem
23
+
24
+ The Interactive 3D Viewer (NiiVue) causes the entire HF Spaces app to hang on "Loading..." forever.
25
+
26
+ ### Root Cause
27
+
28
+ **Gradio does not natively support custom WebGL content.** All hack attempts have **FAILED** (confirmed Dec 10, 2025):
29
+
30
+ | Attempt | Date | Result |
31
+ |---------|------|--------|
32
+ | CDN import in js_on_load | Dec 9 | FAILED - CSP blocked |
33
+ | Vendored + import() in js_on_load | Dec 9 | FAILED - Blocks Svelte hydration |
34
+ | head_paths with loader HTML | Dec 9 | FAILED - Same issue |
35
+ | head= with inline import() | Dec 10 | **FAILED** - Confirmed DOA |
36
+
37
+ Gradio maintainers explicitly closed Issues #4511 (NIfTI support) and #7649 (WebGL canvas) as "not planned", recommending Custom Components instead.
38
+
39
+ **There is no gr.HTML hack that works. The only path forward is a Gradio Custom Component.**
40
+
41
+ ### Solution
42
+
43
+ Build a **Gradio Custom Component** that properly wraps NiiVue using Svelte.
44
+
45
+ See: `docs/specs/28-gradio-custom-component-niivue.md`
46
+
47
+ ### Workaround (Current)
48
+
49
+ The "Static Report" tab (Matplotlib 2D slices) works correctly. Only the "Interactive 3D" tab is broken.
50
+
51
+ ---
52
+
53
  ## Resolved Issues (Fixed in `fix/technical-debt`)
54
 
55
  ### βœ… P2: Silent Empty Dataset on Missing Data Directory
 
92
 
93
  ## Conclusion
94
 
95
+ The codebase is **production-ready for all features EXCEPT the Interactive 3D Viewer (NiiVue)**. All other technical debt items are resolved.
96
+
97
+ **Next step:** Implement Gradio Custom Component per spec #28 to fix the P0 blocker.
docs/specs/{GRADIO_WEBGL_ANALYSIS.md β†’ 24-bug-gradio-webgl-analysis.md} RENAMED
@@ -1,7 +1,23 @@
1
- # Gradio + WebGL/NiiVue Analysis
2
 
3
  **Date:** 2025-12-10
4
- **Context:** Understanding why NiiVue (WebGL) doesn't work in Gradio on HF Spaces
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
 
6
  ---
7
 
 
1
+ # Bug #24: Gradio + WebGL/NiiVue Root Cause Analysis
2
 
3
  **Date:** 2025-12-10
4
+ **Status:** ALL HACKS FAILED - Custom Component Required
5
+ **Issue:** HF Spaces stuck on "Loading..." forever
6
+ **Root Cause:** Gradio does not natively support custom WebGL content
7
+ **Solution:** Build Gradio Custom Component (see spec #28)
8
+
9
+ ---
10
+
11
+ ## CONFIRMED: All gr.HTML Hacks Have Failed
12
+
13
+ | Attempt | Date | Result |
14
+ |---------|------|--------|
15
+ | CDN import in js_on_load | Dec 9 | FAILED - CSP blocks external imports |
16
+ | Vendored + dynamic import() in js_on_load | Dec 9 | FAILED - Blocks Svelte hydration |
17
+ | head_paths with loader HTML | Dec 9 | FAILED - Same hydration issue |
18
+ | head= with inline import() | Dec 10 | **FAILED** - Confirmed DOA |
19
+
20
+ **There is no hack that works.** The only path forward is spec #28 (Gradio Custom Component).
21
 
22
  ---
23
 
docs/specs/28-gradio-custom-component-niivue.md CHANGED
@@ -1,7 +1,7 @@
1
  # Spec #28: Gradio Custom Component for NiiVue
2
 
3
  **Date:** 2025-12-10
4
- **Status:** PROPOSED
5
  **Blocks:** Issue #24 (HF Spaces "Loading..." forever)
6
  **Effort:** Medium (2-3 days)
7
  **Success Probability:** 90%
@@ -10,7 +10,7 @@
10
 
11
  ## Executive Summary
12
 
13
- **The current `gr.HTML` + JavaScript approach will not work reliably.**
14
 
15
  Gradio maintainers have explicitly closed both:
16
  - [Issue #4511](https://github.com/gradio-app/gradio/issues/4511) - NIfTI/medical imaging support β†’ "Not planned"
@@ -301,8 +301,8 @@ viewer = NiiVueViewer(label="Interactive 3D Viewer")
301
 
302
  ### Alternative 1: Keep Hacking gr.HTML
303
  - **Effort:** Low
304
- - **Success probability:** 30%
305
- - **Why rejected:** We've tried 5+ approaches, all failed. Gradio architecture doesn't support this.
306
 
307
  ### Alternative 2: Static HTML Space (No Gradio)
308
  - **Effort:** High (rebuild entire UI)
 
1
  # Spec #28: Gradio Custom Component for NiiVue
2
 
3
  **Date:** 2025-12-10
4
+ **Status:** REQUIRED - All gr.HTML hacks have failed (confirmed Dec 10)
5
  **Blocks:** Issue #24 (HF Spaces "Loading..." forever)
6
  **Effort:** Medium (2-3 days)
7
  **Success Probability:** 90%
 
10
 
11
  ## Executive Summary
12
 
13
+ **All `gr.HTML` + JavaScript approaches have FAILED. This is the only path forward.**
14
 
15
  Gradio maintainers have explicitly closed both:
16
  - [Issue #4511](https://github.com/gradio-app/gradio/issues/4511) - NIfTI/medical imaging support β†’ "Not planned"
 
301
 
302
  ### Alternative 1: Keep Hacking gr.HTML
303
  - **Effort:** Low
304
+ - **Success probability:** 0% (CONFIRMED FAILED)
305
+ - **Why rejected:** We tried 6 approaches over 2 days. ALL failed. This is not a viable path.
306
 
307
  ### Alternative 2: Static HTML Space (No Gradio)
308
  - **Effort:** High (rebuild entire UI)
docs/specs/29-codebase-status-audit.md ADDED
@@ -0,0 +1,272 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Spec #29: Codebase Status Audit (Issue #24 NiiVue/WebGL)
2
+
3
+ **Date:** 2025-12-10
4
+ **Status:** ALL HACKS CONFIRMED FAILED (Dec 10, 2025)
5
+ **Purpose:** Top-down analysis of current frontend/NiiVue implementation state after multiple hotfix attempts
6
+
7
+ ---
8
+
9
+ ## Executive Summary: All gr.HTML Hacks Have Failed
10
+
11
+ After 6 iterations of attempted hotfixes for Issue #24 (HF Spaces "Loading..." forever), **every approach has failed**:
12
+
13
+ | Attempt | Result |
14
+ |---------|--------|
15
+ | CDN import | FAILED - CSP blocked |
16
+ | Vendored + js_on_load import() | FAILED - Blocks hydration |
17
+ | head_paths | FAILED - Same issue |
18
+ | head= with import() | **FAILED** - Confirmed Dec 10 |
19
+
20
+ **The codebase contains dead code from all these failed attempts.** The correct solution is Gradio Custom Component (spec #28).
21
+
22
+ ---
23
+
24
+ ## Current Frontend Architecture
25
+
26
+ ### File Inventory
27
+
28
+ | File | Purpose | Lines | Status |
29
+ |------|---------|-------|--------|
30
+ | `ui/viewer.py` | NiiVue HTML/JS generation | 643 | **BLOATED** - contains 5 approaches |
31
+ | `ui/app.py` | Main Gradio app | 313 | Clean |
32
+ | `ui/components.py` | UI components | 94 | Clean |
33
+ | `app.py` (root) | Local dev entry | 61 | Clean |
34
+ | `ui/assets/niivue.js` | Vendored NiiVue v0.65.0 | 2.9MB | **NECESSARY** |
35
+
36
+ ### What's in `viewer.py` Right Now
37
+
38
+ | Component | Lines | Status | Notes |
39
+ |-----------|-------|--------|-------|
40
+ | `NIIVUE_VERSION` | 30 | OK | Version tracking |
41
+ | `_ASSET_DIR`, `_NIIVUE_JS_PATH` | 31-32 | OK | Path constants |
42
+ | `NIIVUE_JS_URL` | 36 | **UNUSED** | Computed but not actually used |
43
+ | Module-level logging | 39-42 | **SLOP** | 4 log statements at import time |
44
+ | `get_niivue_head_html()` | 45-77 | **PROBLEMATIC** | Still uses `await import()` |
45
+ | `get_niivue_loader_path()` | 80-109 | **DEPRECATED** | Marked deprecated but still exists |
46
+ | `nifti_to_gradio_url()` | 112-142 | OK | Issue #19 fix, working |
47
+ | `get_slice_at_max_lesion()` | 145-187 | OK | Matplotlib helper |
48
+ | `render_3panel_view()` | 190-281 | OK | Matplotlib 3-panel |
49
+ | `render_slice_comparison()` | 284-380 | OK | Matplotlib comparison |
50
+ | `create_niivue_html()` | 383-434 | OK | HTML generation |
51
+ | `NIIVUE_ON_LOAD_JS` | 449-538 | **MOSTLY OK** | No import(), uses window.Niivue |
52
+ | `NIIVUE_UPDATE_JS` | 546-642 | **MOSTLY OK** | No import(), uses window.Niivue |
53
+
54
+ ---
55
+
56
+ ## The Core Problem: `get_niivue_head_html()` Still Uses `import()`
57
+
58
+ The current "fix" in `get_niivue_head_html()` does this:
59
+
60
+ ```javascript
61
+ // viewer.py:63-76
62
+ <script type="module">
63
+ try {
64
+ const niivueUrl = '{NIIVUE_JS_URL}';
65
+ console.log('[NiiVue Loader] Attempting to load from:', niivueUrl);
66
+ const { Niivue } = await import(niivueUrl); // <-- SAME BROKEN PATTERN!
67
+ window.Niivue = Niivue;
68
+ console.log('[NiiVue Loader] Successfully loaded');
69
+ } catch (error) {
70
+ console.error('[NiiVue Loader] FAILED to load:', error);
71
+ window.NIIVUE_LOAD_ERROR = error.message;
72
+ }
73
+ </script>
74
+ ```
75
+
76
+ **This is the EXACT same `await import()` pattern that breaks on HF Spaces.**
77
+
78
+ The only difference from our previous attempts:
79
+ - Before: `await import()` in `js_on_load`
80
+ - Now: `await import()` in `head=` script
81
+
82
+ **Why this might not matter:** The A/B test proved that `js_on_load` with async code breaks Gradio. Moving the `import()` to `head=` might help, but it's still executing async code that could fail silently and leave `window.Niivue` undefined.
83
+
84
+ ---
85
+
86
+ ## What's Necessary vs What's Slop
87
+
88
+ ### NECESSARY (Keep)
89
+
90
+ | Item | Why |
91
+ |------|-----|
92
+ | `ui/assets/niivue.js` | HF Spaces CSP blocks CDN imports |
93
+ | `gr.set_static_paths()` | Required for Gradio 6.x file serving |
94
+ | `nifti_to_gradio_url()` | Issue #19 fix, working |
95
+ | `create_niivue_html()` | Generates viewer HTML |
96
+ | `NIIVUE_ON_LOAD_JS` | Initializes viewer (doesn't import) |
97
+ | `NIIVUE_UPDATE_JS` | Re-initializes after updates |
98
+ | Matplotlib functions | Working 2D fallback |
99
+ | `allowed_paths` in launch() | Runtime file access |
100
+
101
+ ### SLOP (Should Remove/Refactor)
102
+
103
+ | Item | Why It's Slop |
104
+ |------|---------------|
105
+ | `NIIVUE_JS_URL` module-level computation | Computed but unused in production |
106
+ | Module-level logging (lines 39-42) | Noisy startup logs, not useful |
107
+ | `get_niivue_loader_path()` | Deprecated, generates file we don't need |
108
+ | `get_niivue_head_html()` with import() | Still uses broken pattern |
109
+ | Multiple diagnostic docs | Overlapping, contradictory, stale |
110
+
111
+ ### UNCERTAIN (Depends on head= fix working)
112
+
113
+ | Item | Status |
114
+ |------|--------|
115
+ | `head=get_niivue_head_html()` in launch() | **30% chance this works** |
116
+
117
+ ---
118
+
119
+ ## Documentation Status
120
+
121
+ ### docs/specs/ Files
122
+
123
+ | File | Status | Issue |
124
+ |------|--------|-------|
125
+ | `00-context.md` | **ACCURATE** | None |
126
+ | `28-gradio-custom-component-niivue.md` | **ACCURATE** | Just written |
127
+ | `AUDIT_JS_LOADING_ISSUES.md` | **OUTDATED** | Says `set_static_paths` is blocker, but we've moved past that |
128
+ | `DIAGNOSTIC_HF_LOADING.md` | **OUTDATED** | Lists hypotheses we've since disproven |
129
+ | `ROOT_CAUSE_ANALYSIS.md` | **PARTIALLY OUTDATED** | Says "IN PROGRESS", discusses head= as solution |
130
+ | `GRADIO_WEBGL_ANALYSIS.md` | **ACCURATE** | Core analysis, identifies real problem |
131
+
132
+ ### docs/TECHNICAL_DEBT.md
133
+
134
+ | Status | Issue |
135
+ |--------|-------|
136
+ | **OUTDATED** | Claims "Ironclad/Production-Ready" but doesn't mention P0 NiiVue/WebGL blocker |
137
+
138
+ ---
139
+
140
+ ## Recommended Cleanup Actions
141
+
142
+ ### Immediate (If head= fix fails)
143
+
144
+ 1. **Delete deprecated code:**
145
+ - Remove `get_niivue_loader_path()`
146
+ - Remove module-level logging
147
+ - Clean up `NIIVUE_JS_URL` if unused
148
+
149
+ 2. **Archive old diagnostic docs:**
150
+ - Move `AUDIT_JS_LOADING_ISSUES.md` to `archive/`
151
+ - Move `DIAGNOSTIC_HF_LOADING.md` to `archive/`
152
+ - Update `ROOT_CAUSE_ANALYSIS.md` status
153
+
154
+ 3. **Update TECHNICAL_DEBT.md:**
155
+ - Add P0 section for NiiVue/WebGL blocker
156
+ - Link to spec #28 (Custom Component)
157
+
158
+ ### Long-term (After decision on path forward)
159
+
160
+ 1. **If Custom Component route:**
161
+ - Remove all `head=` NiiVue loading code
162
+ - Remove `get_niivue_head_html()`
163
+ - Simplify `viewer.py` to just Matplotlib functions
164
+ - NiiVue loading becomes the component's responsibility
165
+
166
+ 2. **If 2D fallback route:**
167
+ - Remove entire NiiVue integration
168
+ - Remove `ui/assets/niivue.js` (2.9MB)
169
+ - Remove `NIIVUE_ON_LOAD_JS`, `NIIVUE_UPDATE_JS`
170
+ - Keep only Matplotlib rendering
171
+
172
+ ---
173
+
174
+ ## Honest Assessment
175
+
176
+ ### What We've Tried (6+ iterations)
177
+
178
+ 1. **CDN import** β†’ Blocked by CSP
179
+ 2. **Vendored + dynamic import in js_on_load** β†’ Blocks Svelte hydration
180
+ 3. **head_paths with loader HTML** β†’ Complex, didn't work
181
+ 4. **head= with inline import()** β†’ Current state, **probably won't work**
182
+ 5. **Various set_static_paths/allowed_paths combos** β†’ File serving works, JS loading doesn't
183
+
184
+ ### The Pattern
185
+
186
+ Every attempt has been a variation of:
187
+ > "Load NiiVue via some JavaScript mechanism within Gradio"
188
+
189
+ Every attempt has failed because:
190
+ > **Gradio was not designed for custom WebGL content**
191
+
192
+ ### The Correct Solution
193
+
194
+ **Stop fighting Gradio's architecture. Use a Gradio Custom Component.**
195
+
196
+ This is:
197
+ - What Gradio maintainers recommend (Issues #4511, #7649)
198
+ - How existing WebGL components work (gradio-litmodel3d)
199
+ - 90% success probability vs 30% for more hacks
200
+
201
+ See spec #28 for implementation details.
202
+
203
+ ---
204
+
205
+ ## Current Entry Point Flow
206
+
207
+ ```
208
+ HF Spaces Docker
209
+ ↓
210
+ CMD ["python", "-m", "stroke_deepisles_demo.ui.app"]
211
+ ↓
212
+ ui/app.py __main__ block
213
+ ↓
214
+ gr.set_static_paths([_ASSETS_DIR]) # Enable file serving
215
+ ↓
216
+ get_demo() # Creates Blocks with js_on_load components
217
+ ↓
218
+ demo.launch(
219
+ head=get_niivue_head_html(), # <-- Injects <script type="module"> with import()
220
+ allowed_paths=[_ASSETS_DIR],
221
+ )
222
+ ↓
223
+ Browser loads page
224
+ ↓
225
+ <head> script runs: await import('/gradio_api/file=.../niivue.js')
226
+ ↓
227
+ [UNCERTAIN] Does import() succeed? Does it block Svelte?
228
+ ↓
229
+ If yes: window.Niivue is set, js_on_load works
230
+ If no: window.Niivue undefined, viewer shows error
231
+ ```
232
+
233
+ ---
234
+
235
+ ## Files Modified During Issue #24 Debug
236
+
237
+ | File | Changes | Commits |
238
+ |------|---------|---------|
239
+ | `viewer.py` | ~6 rewrites of JS loading approach | Multiple |
240
+ | `ui/app.py` | Added head=, set_static_paths | Multiple |
241
+ | `app.py` | Same as ui/app.py | Multiple |
242
+ | `ui/assets/niivue.js` | Added vendored library | 1 |
243
+ | `.gitignore` | Added niivue-loader.html | 1 |
244
+ | `.pre-commit-config.yaml` | Exclude assets/ from large file check | 1 |
245
+
246
+ ---
247
+
248
+ ## Conclusion
249
+
250
+ **The codebase is messy but not unfixable.** The mess comes from iterating through multiple failed approaches without cleaning up between attempts.
251
+
252
+ **The real issue is architectural:** Gradio + custom WebGL = unsupported pattern.
253
+
254
+ **Next steps:**
255
+ 1. Test if current `head=` approach works on HF Spaces (low confidence)
256
+ 2. If it fails, implement Gradio Custom Component (spec #28)
257
+ 3. Clean up cruft regardless of which path we take
258
+
259
+ ---
260
+
261
+ ## Appendix: How to Verify Current State
262
+
263
+ ```bash
264
+ # Check if NiiVue file serving works
265
+ curl -I "https://[space-url]/gradio_api/file=/home/user/demo/src/stroke_deepisles_demo/ui/assets/niivue.js"
266
+ # Should return 200 OK with application/javascript
267
+
268
+ # Check browser console for:
269
+ # - "[NiiVue Loader] Attempting to load from: ..."
270
+ # - "[NiiVue Loader] Successfully loaded" OR "[NiiVue Loader] FAILED"
271
+ # - Any errors during Gradio initialization
272
+ ```
docs/specs/{AUDIT_JS_LOADING_ISSUES.md β†’ archive/AUDIT_JS_LOADING_ISSUES.md} RENAMED
File without changes
docs/specs/{DIAGNOSTIC_HF_LOADING.md β†’ archive/DIAGNOSTIC_HF_LOADING.md} RENAMED
File without changes
docs/specs/{ROOT_CAUSE_ANALYSIS.md β†’ archive/ROOT_CAUSE_ANALYSIS.md} RENAMED
File without changes