# HF Spaces UI Completely Broken - Consolidated Audit
**Date:** 2025-12-10
**Branch:** `debug/hf-spaces-ui-completely-broken`
**Status:** P0 CRITICAL - ROOT CAUSE IDENTIFIED + EXTERNALLY VALIDATED
---
## TL;DR - THE FIXES (2 Critical Issues)
### Issue 1: Missing `gradio` Prop (Causes UI Freeze)
Our custom component is missing the **`gradio` prop** which provides `i18n` and `autoscroll`. This is 100% required for StatusTracker to work.
**Current broken code:**
```svelte
// WRONG - missing gradio prop
let { value, loading_status, ... }: Props = $props();
// CRASHES - no i18n!
```
**Correct pattern:**
```svelte
// CORRECT - includes gradio prop (can use $props() OR export let)
import type { Gradio } from "@gradio/utils";
let { gradio, loading_status, ... }: Props = $props();
```
### Issue 2: .gitignore Excludes Compiled Templates (Causes Missing Assets on HF Spaces)
**CRITICAL NEW FINDING from external validation:**
`packages/niivueviewer/.gitignore` line 12 has:
```text
backend/**/templates/
```
This means **any rebuilt component templates will NOT be committed**. Even though the current templates are tracked (added before the rule), a `gradio cc build` will create new files that Git ignores.
**Fix:** Remove `backend/**/templates/` from `.gitignore`
---
## Root Cause (CONFIRMED)
### Primary Issue: Missing `gradio` Prop
The custom component **does not declare the `gradio` prop**, which is injected by the parent Gradio application and contains:
- `gradio.i18n` - **REQUIRED** by StatusTracker for text formatting
- `gradio.autoscroll` - Controls scroll behavior
- `gradio.dispatch()` - For emitting events
**Evidence:**
- [Gradio Frontend Guide](https://www.gradio.app/guides/frontend) explicitly shows `gradio` prop is required
- [PDF Component Example](https://www.gradio.app/guides/pdf-component-example) shows working pattern
- [StatusTracker npm](https://www.npmjs.com/package/@gradio/statustracker) confirms `i18n` is required
### Secondary Issue: Svelte 5 Runes vs Svelte 4 Syntax - **REFUTED**
**External validation clarified:** Svelte 5 runes are NOT inherently risky.
Gradio 6 ships with **Svelte 5.43.14**, and `@gradio` packages peer-depend on `svelte ^5.43.4`. Svelte 5 runes (`$props()`, `$effect()`) are supported.
| File | Syntax | Status |
|------|--------|--------|
| Index.svelte | `$props()` (Svelte 5) | **OK** (if gradio prop is added) |
| Example.svelte | `export let` (Svelte 4) | OK (legacy syntax) |
| Gradio PDF example | `export let` (Svelte 4) | Reference (uses older syntax) |
**Verdict:** We can keep `$props()` and `$effect()`. The issue is the missing `gradio` prop, not the syntax choice. However, mixing paradigms within a single package (Index uses runes, Example uses legacy) is inconsistent and should be unified.
---
## All Issues Found (Priority Order) - EXTERNALLY VALIDATED
### P0 - Critical (Will Definitely Break)
| # | Issue | File:Line | Fix |
|---|-------|-----------|-----|
| 1 | **Missing `gradio` prop** | Index.svelte:8-20 | Add `gradio: Gradio<{...}>` to Props interface and destructure |
| 2 | **Missing `i18n` to StatusTracker** | Index.svelte:120-123 | Add `i18n={gradio.i18n}` |
| 3 | **Missing `autoscroll` to StatusTracker** | Index.svelte:120-123 | Replace `autoscroll={false}` with `autoscroll={gradio.autoscroll}` |
| 4 | **`.gitignore` excludes templates/** | `.gitignore:12` | Remove `backend/**/templates/` line |
### P1 - Likely Breaking
| # | Issue | File:Line | Fix |
|---|-------|-----------|-----|
| 5 | No error handling in onMount | Index.svelte:40-50 | Wrap NiiVue init in try/catch |
| 6 | Double loadVolumes call (onMount + $effect) | Index.svelte:49,98-103 | Remove $effect's loadVolumes() or add guard |
### P2 - Code Quality (Should Fix)
| # | Issue | File:Line | Fix |
|---|-------|-----------|-----|
| 7 | Unused template dependencies | frontend/package.json | Remove cropperjs, lazy-brush, resize-observer-polyfill |
| 8 | Example.svelte value shape mismatch | Example.svelte | Update to use {background_url, overlay_url} |
| 9 | Mixed Svelte syntax (Index=runes, Example=legacy) | Both files | Unify to one paradigm |
### P3 - Documentation
| # | Issue | File:Line | Fix |
|---|-------|-----------|-----|
| 10 | TECHNICAL_DEBT.md says P0 resolved | docs/TECHNICAL_DEBT.md:13-33 | Update status to reflect unresolved |
| 11 | Stale diagnostic docs | ROOT_CAUSE_ANALYSIS.md, etc. | Archive or update |
### NOT an Issue (External Validation Refuted)
| # | Previous Claim | Status | Reason |
|---|----------------|--------|--------|
| - | Svelte 5 runes are risky | **REFUTED** | Gradio 6 ships Svelte 5.43.14; runes are supported |
| - | Missing EVENTS breaks component | **LOW PRIORITY** | Output components can work without EVENTS declaration |
---
## Correct Index.svelte Pattern
Based on [Gradio's PDF Component Example](https://www.gradio.app/guides/pdf-component-example) + external validation that Svelte 5 runes are OK:
```svelte
{#if loading_status}
{/if}
```
---
## Python Backend Fix (LOW PRIORITY)
**External validation says:** EVENTS is not required for output-only components. The UI freeze is NOT caused by missing EVENTS.
However, if we want to emit events in the future:
```python
from gradio.events import Events
class NiiVueViewer(Component):
"""WebGL NIfTI viewer using NiiVue."""
EVENTS = [Events.change] # OPTIONAL - add if we want to emit change events
data_model = NiiVueViewerData
# ... rest unchanged
```
**Decision:** Skip for now. Focus on P0 issues first.
---
## Validation Checklist (Updated per External Validation)
### Before Fix
- [ ] `gradio` prop declared? **NO** ← P0
- [ ] `i18n` passed to StatusTracker? **NO** ← P0
- [ ] `autoscroll` passed to StatusTracker? **NO** ← P0
- [ ] `.gitignore` allows templates/? **NO** (ignored!) ← P0
- [ ] onMount has error handling? **NO** ← P1
- [ ] Double loadVolumes prevented? **NO** ← P1
### After Fix (Target)
- [x] `gradio` prop declared? YES
- [x] `i18n` passed to StatusTracker? YES
- [x] `autoscroll` passed to StatusTracker? YES
- [x] `.gitignore` allows templates/? YES
- [x] onMount has error handling? YES
- [x] Double loadVolumes prevented? YES (via `initialized` guard)
### Not Required (Per External Validation)
- Svelte 4 syntax? **NOT REQUIRED** - Svelte 5 runes are OK with Gradio 6
- EVENTS in Python? **NOT REQUIRED** - output components work without it
---
## Test Plan
### Step 1: Local Test
```bash
cd /Users/ray/Desktop/CLARITY-DIGITAL-TWIN/stroke-deepisles-demo
uv run python -m stroke_deepisles_demo.ui.app
```
Open and verify:
- [ ] Page loads without freezing
- [ ] Dropdown is clickable
- [ ] Buttons respond
- [ ] NiiVue canvas initializes (shows black, not "loading...")
### Step 2: Rebuild Component
```bash
cd packages/niivueviewer
gradio cc build
```
### Step 3: HF Spaces Deploy
Push to HF Spaces only after local test passes.
---
## External Audit Validation (Two Independent Agents Confirmed)
| Claim | Validated | Evidence |
|-------|-----------|----------|
| StatusTracker requires `i18n` | **TRUE** | @gradio/statustracker npm package requires i18n with no default; compiled JS dereferences `e.i18n()` |
| `gradio` prop provides `i18n` | **TRUE** | Official PDF Component Example shows `gradio.i18n` pattern |
| Svelte 5 runes cause issues | **REFUTED** | Gradio 6 ships Svelte 5.43.14; @gradio packages peer-depend on svelte ^5.43.4 |
| Missing EVENTS breaks component | **REFUTED** | Output components can work without EVENTS; not relevant to UI freeze |
| `.gitignore` excludes templates/ | **TRUE** | `packages/niivueviewer/.gitignore:12` has `backend/**/templates/` |
| `demo.load()` blocks hydration | **FALSE** | Runs after render, not the cause |
### Web Search Validation Sources
- Gradio GitHub Issue #6609: Hydration freezes when frontend errors occur before StatusTracker renders
- Gradio GitHub Issue #7649: WebGL must be handled via custom component
- Gradio GitHub Issue #11319: Spaces failures when templates not packaged
- @gradio/statustracker npm tarball: Props interface confirms `i18n` required with no default
---
## References
- [Gradio Frontend Guide](https://www.gradio.app/guides/frontend) - Shows `gradio` prop pattern
- [Gradio PDF Component Example](https://www.gradio.app/guides/pdf-component-example) - Complete working example
- [Gradio Backend Guide](https://www.gradio.app/guides/backend) - EVENTS documentation
- [Gradio StatusTracker npm](https://www.npmjs.com/package/@gradio/statustracker) - Package details
- [Gradio Custom Components](https://www.gradio.app/guides/custom-components-in-five-minutes) - Getting started
- [Gradio i18n](https://www.gradio.app/guides/internationalization) - Internationalization docs
---
## Files to Modify
| File | Change |
|------|--------|
| `packages/niivueviewer/frontend/Index.svelte` | Rewrite with `gradio` prop, Svelte 4 syntax |
| `packages/niivueviewer/backend/gradio_niivueviewer/niivueviewer.py` | Add `EVENTS = [Events.change]` |
---
## WHY WAS THIS MISSED? (Root Cause of Root Cause)
### The Spec Itself Was Incomplete
**Spec #28** (`docs/specs/28-gradio-custom-component-niivue.md`) provided implementation guidance that **did not include the `gradio` prop**. Looking at lines 176-296 of the spec:
```svelte
// What the spec showed (WRONG):
export let value: {...} | null = null;
// ... custom loading/error divs
// NO gradio prop
// NO StatusTracker
```
The spec **did NOT read the official Gradio Frontend Guide thoroughly**. It showed custom loading/error handling instead of using Gradio's official `StatusTracker` component.
### The Implementation Improved On The Spec (But Still Wrong)
The actual implementation in `Index.svelte` **correctly** chose to use:
- `Block` component wrapper (correct)
- `StatusTracker` component (correct approach)
But then **didn't research what StatusTracker requires**:
- Missing `gradio` prop declaration
- Missing `i18n={gradio.i18n}`
- Missing `autoscroll={gradio.autoscroll}`
### The Fundamental Failure
| Stage | What Happened | What Should Have Happened |
|-------|---------------|---------------------------|
| Spec Writing | Read "Custom Components in 5 Minutes" | Read FULL Frontend Guide + PDF Example |
| Spec Review | Focused on NiiVue/WebGL integration | Verified against working Gradio components |
| Implementation | Used StatusTracker without research | Checked StatusTracker npm package requirements |
| Testing | Tested locally (worked) | Should have checked browser console for errors |
---
## The Correct Prompting Approach (For Future Reference)
### What We Asked
```text
Create a Gradio Custom Component for NiiVue WebGL viewer
```
### What We Should Have Asked
```markdown
## Task: Create a Gradio Custom Component for NiiVue
### BEFORE writing ANY code:
1. **READ these official Gradio docs IN FULL** (not skimming):
- https://www.gradio.app/guides/frontend (CRITICAL - shows gradio prop)
- https://www.gradio.app/guides/pdf-component-example (working reference)
- https://www.gradio.app/guides/backend (EVENTS documentation)
2. **IDENTIFY all required props** that Gradio passes to components:
- `value` - component's data
- `loading_status` - if using StatusTracker
- `gradio` - **CRITICAL**: provides i18n, autoscroll, dispatch()
- Other props: elem_id, elem_classes, visible, etc.
3. **If using StatusTracker**, VERIFY what props it requires:
- Check @gradio/statustracker npm package
- Confirm `i18n` is REQUIRED (no default value)
- Confirm `autoscroll` is expected
4. **Use Svelte 4 syntax** (`export let`) NOT Svelte 5 runes (`$props`, `$effect`)
- ALL Gradio examples use Svelte 4
- Svelte 5 runes are undocumented with Gradio components
5. **For Python backend**, check if EVENTS are needed:
- Components that emit events need `EVENTS = [Events.change]`
### VERIFICATION before claiming "done":
- [ ] Compare Index.svelte line-by-line against PDF Component Example
- [ ] Confirm gradio prop is declared
- [ ] Confirm i18n is passed to StatusTracker
- [ ] Check browser console for JavaScript errors
- [ ] Test on fresh browser (not cached)
```
---
## Lessons Learned (Updated Post-Validation)
### 1. Official Documentation Is Not Optional
The agent assumed it knew Gradio patterns from general knowledge. It did NOT thoroughly read the official docs. **The `gradio` prop is clearly documented** in the Frontend Guide.
### 2. "Works Locally" ≠ "Works Correctly"
The component loaded locally because the browser cached state or errors were swallowed. A fresh test or HF Spaces deployment revealed the crash.
### 3. Using Components Requires Understanding Their Contracts
`StatusTracker` is a black box. The agent used it without checking:
- What props it requires
- What happens if `i18n` is undefined
- Whether it has default values
### 4. Spec Review Must Validate Against Official Examples
The spec was reviewed for:
- ✅ File structure
- ✅ Build process
- ✅ HF Spaces deployment
- ❌ **Component prop requirements** (MISSED)
- ❌ **StatusTracker requirements** (MISSED)
- ❌ **`.gitignore` patterns** (MISSED - templates were ignored!)
### 5. ~~Svelte 5 Runes Are Risky With Gradio~~ **REFUTED**
**Correction:** External validation proved Gradio 6 ships with Svelte 5.43.14 and supports runes. The issue was the missing `gradio` prop, not the syntax choice.
### 6. NEW: Check .gitignore Before Assuming Files Are Committed
The templates/ directory was ignored by `.gitignore`. Even though existing files were tracked, any rebuild would create new files that Git ignores. **Always verify .gitignore patterns when dealing with build artifacts.**
### 7. NEW: External Validation Is Critical for Complex Issues
Two independent external agents validated our findings and caught:
- The `.gitignore` issue we missed
- Refuted the Svelte 5 concern
- Confirmed the exact mechanism (i18n deref crash)
---
## Summary (Post External Validation)
**Root Cause #1:** The UI freeze is caused by StatusTracker crashing due to missing `i18n` prop. The `i18n` is provided by the `gradio` prop, which our component never declares. The compiled JS dereferences `e.i18n()` which throws a TypeError, blocking Svelte hydration.
**Root Cause #2:** The `.gitignore` file ignores `backend/**/templates/`, meaning any rebuilt component assets won't be committed. This is a deployment time bomb.
**Why it was missed:** The spec didn't thoroughly read official Gradio documentation OR check `.gitignore` patterns.
**The fix is straightforward (P0 only):**
1. Add `gradio: Gradio<{...}>` to Props interface in Index.svelte
2. Pass `gradio.i18n` and `gradio.autoscroll` to StatusTracker
3. Remove `backend/**/templates/` from `.gitignore`
4. Rebuild with `gradio cc build`
5. Commit the new templates
**NOT required (per external validation):**
- Converting to Svelte 4 syntax (Svelte 5 runes are fine)
- Adding EVENTS to Python backend (output components don't need it)
**For future custom components:** Always read the official Frontend Guide and PDF Component Example FIRST, verify your implementation matches, AND check `.gitignore` patterns for build artifacts.