---
name: debug-frontend
description: Diagnose and auto-fix SmartStack React+Vite frontend failures
group: DEBUG
allowed-tools: [Read, Edit, Write, Glob, Grep, Bash]  # Bash: diagnostic commands
---

# Skill: Debug Frontend — React + Vite + SmartStack

## Context

You operate in the worktree of a SmartStack client project. The frontend is a
React 19 + Vite 7 project in `web/{appcode}-web/`, with `@atlashub/smartstack` as the lib.
The Studio dev-runner spawns `npm run dev -- --port {port} --host localhost` with
`env.VITE_API_URL=http://localhost:{apiPort}`.

## Execution Plan

### Step 1 — Scaffold Integrity

Verify the presence of the 5 critical files:

```bash
cd web/{appcode}-web
ls index.html src/main.tsx src/App.tsx package.json vite.config.ts 2>&1
```

| Missing File | Action |
|--------------|--------|
| `index.html` | **CRITICAL**. Create with `<div id="root"></div>` + `<script type="module" src="/src/main.tsx">` |
| `src/main.tsx` | Generate from template with `BrowserRouter > SmartStackProvider config={{apiUrl: import.meta.env.VITE_API_URL}}` |
| `src/App.tsx` | Create with `<DynamicRouter />` from `@atlashub/smartstack` |
| `package.json` | Broken project — user must re-run `ss init` |
| `vite.config.ts` | Create with React plugin + proxy `/api` → backend |

### Step 2 — Dependencies

```bash
ls node_modules/@atlashub/smartstack/package.json 2>&1
```

If missing:
```bash
npm install
```

### Step 3 — Config main.tsx

Open `src/main.tsx` and verify this exact structure:

```tsx
import { BrowserRouter } from 'react-router-dom';
import { SmartStackProvider } from '@atlashub/smartstack';

const config = {
  apiUrl: import.meta.env.VITE_API_URL || 'http://localhost:5142',
  appName: '{AppCode}',
};

createRoot(document.getElementById('root')!).render(
  <BrowserRouter>
    <SmartStackProvider config={config}>
      <App />
    </SmartStackProvider>
  </BrowserRouter>
);
```

**Common Errors**:

| Runtime Symptom | Cause | Fix |
|-----------------|-------|-----|
| `useLocation() may be used only in the context of a <Router> component` | Missing `<BrowserRouter>` | Wrap `<SmartStackProvider>` in `<BrowserRouter>` |
| `Cannot read properties of undefined (reading 'extensions')` | Missing `config` prop on `<SmartStackProvider>` | Pass `config={{apiUrl: ...}}` |
| `Cannot read properties of undefined (reading 'apiUrl')` | Config passed but `apiUrl` missing | Add `apiUrl: import.meta.env.VITE_API_URL` |
| `You cannot render a <Router> inside another <Router>` | Duplicate BrowserRouter (main.tsx + App.tsx) | Remove the second one |

### Step 4 — Launch Vite

```bash
npm run dev -- --port 3000 --host localhost
```

**Analyze the output**:

| Vite Line | Interpretation |
|-----------|----------------|
| `Port 3000 is in use, trying another one...` | Port occupied, Vite bumps. Studio URL will be out of sync. |
| `Local:   http://localhost:3000/` | ✓ Vite is listening on this port |
| `error when starting dev server: [vite]...` | Vite config bug — check `vite.config.ts` |
| `Failed to resolve import "..."` | Missing package — `npm install` |

### Step 5 — Verify Rendering

```bash
curl -fsS http://localhost:3000/
```

Expected: HTML containing `<div id="root"></div>` and the main.tsx script.

| Response | Diagnosis |
|----------|-----------|
| HTTP 200 + HTML with `#root` | ✓ OK |
| HTTP 404 | `index.html` missing at web project root (NOT in public/) |
| ECONNREFUSED | Vite is not running — loop back to Step 4 |
| HTML without `#root` | `index.html` corrupted — regenerate |

### Step 6 — Check React Runtime Errors

If the page loads but the app doesn't display:
1. Open the browser DevTools (or parse frontend logs)
2. Look for red error messages
3. Apply fixes from the "Common Errors" table above

### Step 7 — Blank Page (Silent Spinner / Empty Render)

Most common SmartStack bug: the page loads (HTTP 200, HTML with `#root`),
no console error, but the screen stays empty OR shows an infinite spinner.
Root cause (almost always): `DynamicRouter` cannot resolve the `componentKey` returned
by the navigation API to a registered component.

#### Diagnosis in 4 Checks

```bash
# 1. Does the navigation API respond correctly?
curl -fsS http://localhost:{apiPort}/api/navigation/menu | head -80
```

Expected: JSON with `applications[].modules[].sections[].componentKey` populated.
If the `componentKey` field is empty/null or the JSON is `{}` → backend issue
(seed not run, user not logged in, wrong tenant). Go to `debug/backend`.

```bash
# 2. Is the registry properly imported in main.tsx?
grep -n "componentRegistry\|Registry" web/{appcode}-web/src/main.tsx
```

Expected: at least one line `import './extensions/{module}Registry';` (or equivalent)
BEFORE `createRoot(...).render(...)`. If missing → PageRegistry is empty at boot,
DynamicRouter finds no pages.

```bash
# 3. Do Registry files exist and call PageRegistry.register()?
ls web/{appcode}-web/src/extensions/*Registry*.ts* 2>&1
grep -l "PageRegistry.register" web/{appcode}-web/src/extensions/ 2>&1
```

Expected: one file per module, each containing multiple lines
`PageRegistry.register('{app}.{module}.{section}[.{view}]', {PageName});`.
If the file exists but has no `register()` → scaffold-routes CLI
didn't run or failed silently.

```bash
# 4. Do registered keys match the API componentKeys?
curl -fsS http://localhost:{apiPort}/api/navigation/menu \
  | grep -oE '"componentKey":\s*"[^"]+"' | sort -u
grep -hE "PageRegistry.register\('[^']+'" web/{appcode}-web/src/extensions/*.ts \
  | sed -E "s/.*register\('([^']+)'.*/\1/" | sort -u
```

Compare the two lists. Any key in the API but missing from registry
= guaranteed blank page for that route. The reverse (registered but not in API)
= page declared but never reachable via navigation (not critical but wasted).

#### Diagnosis Table

| Symptom | Likely Cause | Fix |
|---------|--------------|-----|
| Blank screen on entire app, no console error | `import './extensions/*Registry'` missing from `main.tsx` | Add the import BEFORE `render()` |
| Blank screen on specific route, others OK | The key `{app}.{module}.{section}` not in a `PageRegistry.register(...)` | Re-run `scaffold-routes` for this module |
| Infinite spinner on a route, console silent | Case/dash mismatch between `componentKey` API and registered key | Regenerate both sides (seed + routes) from the same spec |
| `/api/navigation/menu` returns `{}` or 401 | Seed not run or auth not established | `debug/backend` |
| Route with `/:id` shows empty but list route OK | `.detail` page not registered or component uses `:userId` instead of `:id` | Verify the key `{...}.detail` + `useParams<{ id: string }>()` |
| `TypeError: Cannot read properties of undefined (reading 'default')` | `lazy(() => import(...))` resolves to module without default or named export | Use the form `lazy(() => import().then(m => ({ default: m.Named ?? m.default })))` |

#### Final Verification

After fixing, reload the page and verify in the browser console:
```
PageRegistry.list() // should return a non-empty Array
```

If the registry exposes this debug method, confirm the expected key
is in it. Otherwise, add a `console.log('registry size', ...)` to the
Registry file in question, recompile, re-validate.

## Key Files

```
web/{appcode}-web/
├── index.html              # ⚠ REQUIRED at root — not in public/
├── vite.config.ts          # React plugin + server.port + proxy /api
├── package.json            # deps: react, react-dom, react-router-dom, @atlashub/smartstack
├── src/
│   ├── main.tsx            # BrowserRouter > SmartStackProvider config={...} > App
│   ├── App.tsx             # <DynamicRouter />
│   └── index.css           # Global styles (Tailwind)
└── .env.development        # VITE_API_URL (overridden at runtime by dev-runner)
```

## Final Validation

Before saying "fixed":
1. `curl http://localhost:3000/` → HTTP 200 + HTML with `#root` and module script
2. No red errors in Vite stdout
3. If possible, test an API call: `curl http://localhost:3000/api/health` (via Vite proxy)

## Invariant Rules

- **Never** delete `index.html` without a replacement
- **Never** install packages without explicit validation
- **Do not touch** `.bak` files (auto-repair backups)
- **VITE_API_URL** is injected by the dev-runner — do not hardcode it in prod
- After fix: re-run `npm run dev` and re-verify
