Files
GMW/docs/superpowers/specs/2026-07-02-astro-migration-design.md
T

360 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Astro Migration — BETE Design System Implementation
> Migrasi frontend React SPA ke Astro SSG dengan React islands, mengikuti design system di `design/`.
**Date:** 2026-07-02
**Status:** Draft
**Owner:** @asephs
---
## 🎯 Ringkasan
Migrasi frontend dari React 19 + Vite (+ Tailwind 4) ke **Astro SSG** dengan React islands untuk komponen interaktif. Mengganti `services/frontend` yang sekarang dengan struktur Astro yang baru. Semua desain dari `design/` (16 dokumen) diimplementasikan sebagai fondasi visual.
---
## 🔗 Referensi Desain
Dokumen desain yang menjadi acuan implementasi:
| Dokumen | Konsep Kunci |
|---------|-------------|
| `core/01-color-system.md` | OKLCH colors, glassmorphism, semantic tokens |
| `core/02-typography.md` | Fluid type scale (clamp), Outfit + JetBrains Mono |
| `core/03-spatial-system.md` | 4px baseline grid, spacing/radius/z-index tokens |
| `core/04-motion-system.md` | Easing curves, duration tokens, micro-interactions |
| `core/05-component-architecture.md` | Atomic design taxonomy |
| `patterns/06-interaction-patterns.md` | Interaction feedback matrix, keyboard shortcuts |
| `patterns/09-state-machines.md` | Quad-state pattern (loading/error/empty/success) |
| `patterns/10-responsive-system.md` | Breakpoints, mobile-nav, container queries |
| `services/11-frontend-ui.md` | Tech stack, Tailwind config, glass utilities |
| `system/15-theme-architecture.md` | CSS vars, dark/light theme switching |
---
## 🛠️ Tech Stack
| Tool | Versi | Peran |
|------|-------|-------|
| Astro | 5.x | Meta-framework, SSG, routing, component model |
| React | 19.x | Interactive islands (WebSocket, canvas, state) |
| TypeScript | 5.x | Type safety |
| Tailwind CSS | 4.x | Utility-first CSS (`@theme` block) |
| Zustand | 5.x | Client state (UI, voice, messages) |
| TanStack Query | 5.x | Server state (API data fetching) |
| Radix UI | — | Headless primitives (Modal, Dropdown, Tabs) |
| Framer Motion | 11.x | Complex animations (islands only) |
| Three.js | 0.170+ | Particle background (deferred island) |
---
## 🚧 Pendekatan: Astro-First + React Islands
**Prinsip:**
- Semua komponen **non-interaktif**`.astro` (zero JavaScript)
- Komponen **interaktif** → React islands (`src/islands/`) dengan client directives
- Routing → file-based Astro pages
- Data fetching → REST API dari browser (SSG tidak punya backend runtime)
### React Islands per Fitur
| Island | Client Directive | Alasan |
|--------|-----------------|--------|
| AuthGuard | `client:load` | Cek auth state on mount |
| VoiceControls | `client:load` | WebSocket + voice interaction |
| AudioVisualizer | `client:idle` | Canvas-based, bukan prioritas awal |
| MessageFeed | `client:load` | WebSocket + infinite scroll |
| MascotChat | `client:idle` | Secondary feature |
| ThemeToggle | `client:idle` | Non-critical UI |
| Particles | `client:idle` | Background decoration |
| ActiveSpeakers | `client:idle` | WS-driven list |
| NowPlaying | `client:idle` | Media state |
---
## 📁 Struktur Proyek
```
services/frontend/
├── astro.config.ts
├── package.json
├── tsconfig.json
├── src/
│ ├── pages/
│ │ ├── index.astro → Dashboard (Live tab sebagai default)
│ │ ├── live.astro → Live panel
│ │ ├── messages.astro → Message feed
│ │ ├── settings.astro → Settings
│ │ ├── login.astro → Auth page
│ │ ├── recordings.astro → Recordings
│ │ └── 404.astro
│ │
│ ├── layouts/
│ │ ├── DashboardLayout.astro # Sidebar + Header + <slot/>
│ │ └── AuthLayout.astro # Minimal, centered card
│ │
│ ├── components/ # Astro components — zero JS
│ │ ├── ui/
│ │ │ ├── Button.astro
│ │ │ ├── Badge.astro
│ │ │ ├── SeverityBadge.astro
│ │ │ ├── Card.astro
│ │ │ ├── Skeleton.astro
│ │ │ └── Spinner.astro
│ │ ├── sidebar/
│ │ │ ├── Sidebar.astro
│ │ │ └── NavItem.astro
│ │ ├── header/
│ │ │ └── Header.astro
│ │ └── states/
│ │ ├── EmptyState.astro
│ │ ├── ErrorState.astro
│ │ └── LoadingSkeleton.astro
│ │
│ ├── islands/ # React components — interactive
│ │ ├── AuthGuard.tsx # client:load
│ │ ├── VoiceControls.tsx # client:load
│ │ ├── AudioVisualizer.tsx # client:idle
│ │ ├── MessageFeed.tsx # client:load
│ │ ├── MascotChat.tsx # client:idle
│ │ ├── ThemeToggle.tsx # client:idle
│ │ ├── Particles.tsx # client:idle
│ │ ├── ActiveSpeakers.tsx # client:idle
│ │ └── NowPlaying.tsx # client:idle
│ │
│ ├── stores/ # Zustand stores
│ │ ├── ui-store.ts
│ │ ├── voice-store.ts
│ │ └── message-store.ts
│ │
│ ├── shared/
│ │ ├── api/client.ts # REST API calls
│ │ ├── ws/socket.ts # WebSocket manager
│ │ ├── hooks/
│ │ │ ├── useMessages.ts
│ │ │ ├── useVoiceStatus.ts
│ │ │ ├── useMediaControl.ts
│ │ │ ├── useUIState.ts
│ │ │ └── useReducedMotion.ts
│ │ └── lib/utils.ts # cn(), formatters
│ │
│ └── styles/
│ └── base.css # Tailwind + design tokens + glass
└── public/
└── favicon.svg
```
---
## 🎨 CSS Architecture
### Layer Stack
```
base.css
├── @import "tailwindcss"
├── @theme {} → Tailwind 4 semantic tokens
├── :root {} → Dark theme CSS vars (OKLCH)
├── [data-theme="light"] {} → Light theme overrides
├── @layer base → Reset, font-face, scrollbar styling
├── @layer components → Glass, gradient-text, typography classes
└── @layer utilities → Animations keyframes
```
### CSS Variables: Prefix Categories
| Kategori | Prefix | Contoh |
|----------|--------|--------|
| Warna | `--clr-*` | `--clr-surface-base`, `--clr-primary` |
| Spacing | `--sp-*` | `--sp-3` (16px), `--sp-5` (32px) |
| Typography | `--fs-*`, `--fw-*`, `--lh-*` | `--fs-base`, `--fw-semibold` |
| Radius | `--rd-*` | `--rd-md` (8px), `--rd-xl` (16px) |
| Shadow | `--sh-*` | `--sh-card`, `--sh-modal` |
| Z-index | `--z-*` | `--z-header` (30), `--z-modal` (60) |
| Timing | `--dur-*` | `--dur-fast` (150ms) |
| Easing | `--ease-*` | `--ease-out`, `--ease-out-quint` |
### Glass Utility Classes
```css
.glass {
background: oklch(from var(--clr-surface-elevated) l c h / 0.6);
backdrop-filter: blur(16px);
border: 1px solid oklch(from var(--clr-border) l c h / 0.2);
}
.gradient-text { /* ... */ }
```
---
## 🧩 Component Design (Key Components)
### Button (Astro)
```astro
---
export interface Props {
variant?: 'primary' | 'secondary' | 'destructive' | 'outline' | 'ghost';
size?: 'sm' | 'default' | 'lg' | 'icon';
disabled?: boolean;
class?: string;
}
const { variant = 'primary', size = 'default', disabled = false, class: className = '' } = Astro.props;
---
<button
class:list={[`button button--${variant} button--${size}`, className]}
{disabled}
>
<slot />
</button>
<style>
.button {
display: inline-flex; align-items: center; justify-content: center; gap: var(--sp-1);
border-radius: var(--rd-md); font-size: var(--fs-sm); font-weight: var(--fw-medium);
transition: transform var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out);
}
.button--primary { background: var(--clr-primary); color: var(--clr-text-on-primary); }
.button--secondary { background: var(--clr-interactive-hover); color: var(--clr-text); }
.button--ghost { background: transparent; color: var(--clr-text); }
.button:disabled { opacity: 0.5; cursor: not-allowed; }
.button:focus-visible { outline: 2px solid var(--clr-primary-400); outline-offset: 2px; }
</style>
```
### Card (Astro — Slot-based)
```astro
---
export interface Props {
variant?: 'default' | 'elevated' | 'glass' | 'interactive';
padding?: 'sm' | 'default' | 'lg' | 'none';
class?: string;
}
---
<div class:list={[`card card--${variant}`, Astro.props.class]}>
<slot />
</div>
<style>
.card { background: var(--clr-surface-elevated); border: 1px solid var(--clr-border); border-radius: var(--rd-lg); }
.card--glass { background: var(--glass-bg); backdrop-filter: blur(16px); }
.card--interactive:hover { transform: translateY(-2px); box-shadow: var(--sh-hover); }
</style>
```
### MessageFeed (React Island — most complex)
```tsx
// islands/MessageFeed.tsx
// Keeps the existing MessageFeed logic but imports types from @bete/shared
// Uses useInfiniteQuery for cursor pagination
// Subscribes to WebSocket via SocketManager for real-time updates
// Integrates with Zustand message-store for optimistic updates
// Renders: cards with severity badges, action buttons, attachment previews
// States: loading → skeleton, error → retry, empty → mascot, success → list
interface MessageFeedProps {
channelId?: string;
}
```
---
## 🔌 Data Flow
### WebSocket Architecture
```
Browser → WebSocket → Backend (Express + ws)
↑ ↓
└─────────────────────────┘
(real-time events via SocketManager)
```
- `SocketManager` singleton dengan exponential backoff reconnect
- Events dipetakan ke Zustand stores → trigger React re-render
- REST API untuk initial data fetch (TanStack Query)
### Authentication
- Password disimpan di localStorage
- `X-Admin-Password` header di semua API calls
- AuthGuard island: cek localStorage, verify via `/api/health`, redirect ke login
---
## 📱 Responsive Behavior
| Viewport | Sidebar | Header | Grid | Navigation |
|----------|---------|--------|------|------------|
| < 640px | Bottom tab (56px) | Compact | 1 col | Tab bar |
| 640768px | Bottom tab | Compact | 1-2 col | Tab bar |
| 7681024px | Icon (64px) | Standard | 2-3 col | Sidebar icon |
| 10241280px | Full (256px) | Standard | 3 col | Sidebar full |
| 1280px+ | Full (256px) | Full | 3-4 col | Sidebar full |
---
## 📦 Fase Migrasi (5 Fase)
### Fase 1: Foundation 🔧
- Inisialisasi Astro + React integration
- `base.css` dengan design tokens + Tailwind 4 `@theme`
- DashboardLayout, Sidebar, Header (Astro komponen)
- Theme switching (island)
### Fase 2: UI Component Library 🧱
- Semua Astro components: Button, Badge, Card, Skeleton, SeverityBadge
- State components: EmptyState, ErrorState, LoadingSkeleton
- Glass utilities + animation keyframes
### Fase 3: Auth & Messages 📨
- AuthGuard island (login, localStorage)
- MessageFeed island (TanStack Query + WebSocket)
- Infinite scroll + real-time updates
### Fase 4: Live / Voice 🎤
- VoiceControls island (connect/disconnect)
- ActiveSpeakers, AudioVisualizer, NowPlaying islands
- Recordings page
### Fase 5: Polish ✨
- 3D Particles (three.js, deferred)
- MascotChat island
- View Transitions
- Settings page
- Mobile bottom tab bar
---
## ⚠️ Known Risks & Mitigations
| Risk | Dampak | Mitigasi |
|------|--------|----------|
| WebSocket reconnect di SSG | Koneksi terputus saat navigasi | SocketManager global singleton persist across islands |
| Bundle size React islands | JS besar di halaman interaktif | Split per halaman, gunakan `client:idle` bila memungkinkan |
| Auth check blocking render | Flash of login page | AuthGuard render spinner dulu, baru cek localStorage |
| Design token mismatch | Warna/spasi berbeda dari desain | CSS variables sebagai single source of truth, verifikasi visual tiap fase |
| react-three-fiber compatibility | Mungkin perlu workaround SSR | Island di-defer via `client:idle`, Three.js murni client-side |
---
## ✅ Spec Self-Review
- **Placeholder scan:** All sections filled. No TBD/TODO left.
- **Consistency:** All design tokens reference the same CSS variable naming from `design/`. Component architecture matches atomic taxonomy in `05-component-architecture.md`. State patterns follow `09-state-machines.md`.
- **Scope:** Focused on frontend migration only. Backend/gateway unchanged. All existing features preserved.
- **Ambiguity resolved:**
- Astro output mode: SSG (confirmed)
- React islands scope: only truly interactive components (confirmed)
- All existing features retained (confirmed)
---
*Spec ditulis berdasarkan brainstorming dan persetujuan 7 section design.*