Files
Autonomous-Bug-Explorer/.ralph/specs/phase-10-frontend-shell.md

4.3 KiB

Phase 10: Frontend Shell — shadcn/ui

Setup shadcn/ui

cd frontend
npx shadcn@latest init
# Responder: Vite, Zinc, CSS variables, YES to tailwind

Layout principal

┌────────────────────────────────────────────────┐
│ TopBar: [☰] [ABE logo] ···· [⌘K Search] [🌙] [👤]│
├────────┬───────────────────────────────────────┤
│        │                                       │
│ Side-  │          Content Area                 │
│ bar    │          (React Router Outlet)         │
│        │                                       │
│ 📊 Dashboard                                   │
│ 🔍 Explorations                                │
│ 🐛 Findings                                    │
│ 📄 Reports                                     │
│ ─────────                                      │
│ ⚙️ Settings                                    │
│        │                                       │
└────────┴───────────────────────────────────────┘

Dark mode (DEFAULT)

  • Usar estrategia class-based: <html class="dark">
  • CSS variables de shadcn ya soportan dark mode
  • Toggle en TopBar: sol/luna
  • Persistir en localStorage key 'abe-theme'

Auth flow en frontend

App monta →
  GET /api/auth/setup-required
    → si required: mostrar /setup
    → si no:
      GET /api/auth/me
        → si 401: redirect /login
        → si ok: render AppLayout con user data

API client (lib/api.ts)

const API_URL = import.meta.env.VITE_API_URL || '';

export async function apiFetch<T>(path: string, init?: RequestInit): Promise<T> {
  const res = await fetch(`${API_URL}${path}`, {
    ...init,
    credentials: 'include',
    headers: { 'Content-Type': 'application/json', ...init?.headers },
  });
  
  if (res.status === 401) {
    window.location.href = '/login';
    throw new Error('Unauthorized');
  }
  
  if (!res.ok) {
    const body = await res.json().catch(() => ({}));
    throw new Error(body.message || `HTTP ${res.status}`);
  }
  
  return res.json();
}

Routing

<Routes>
  <Route path="/login" element={<Login />} />
  <Route path="/setup" element={<Setup />} />
  <Route element={<ProtectedRoute><AppLayout /></ProtectedRoute>}>
    <Route path="/" element={<Dashboard />} />
    <Route path="/sessions" element={<SessionList />} />
    <Route path="/sessions/:id" element={<SessionDetail />} />
    <Route path="/findings" element={<FindingsList />} />
    <Route path="/findings/:id" element={<FindingDetail />} />
    <Route path="/reports" element={<Reports />} />
    <Route path="/visual-review" element={<VisualReview />} />
    <Route path="/settings/*" element={<Settings />} />
  </Route>
</Routes>

Command Palette (⌘K)

Powered by shadcn Command (cmdk):

  • Buscar por: sessions, findings, settings sections
  • Acciones: "New Exploration", "Generate Report"
  • Keyboard: ⌘K abre, Esc cierra

File structure

frontend/src/
├── components/
│   ├── ui/              # shadcn generados (NO tocar)
│   ├── layout/
│   │   ├── AppLayout.tsx
│   │   ├── AppSidebar.tsx
│   │   ├── TopBar.tsx
│   │   ├── CommandPalette.tsx
│   │   ├── ProtectedRoute.tsx
│   │   └── ThemeProvider.tsx
│   └── common/
│       └── SeverityBadge.tsx
├── hooks/
│   ├── useAuth.ts
│   └── useSocket.ts
├── lib/
│   ├── api.ts
│   ├── queryClient.ts
│   └── utils.ts         # cn() de shadcn
├── stores/
│   └── uiStore.ts
├── pages/
│   ├── Dashboard.tsx     (placeholder "Coming in Phase 11")
│   ├── Login.tsx
│   └── Setup.tsx
├── App.tsx
└── main.tsx

IMPORTANTE

  • El Dashboard en esta fase puede ser un placeholder que diga "Dashboard — Coming soon"
  • Lo importante es que el shell funcione: login → sidebar → routing → theme
  • NO intentar hacer todo el dashboard aquí — eso es Phase 11