# 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 + │ │ └── 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; --- ``` ### Card (Astro — Slot-based) ```astro --- export interface Props { variant?: 'default' | 'elevated' | 'glass' | 'interactive'; padding?: 'sm' | 'default' | 'lg' | 'none'; class?: string; } ---
``` ### 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 | | 640–768px | Bottom tab | Compact | 1-2 col | Tab bar | | 768–1024px | Icon (64px) | Standard | 2-3 col | Sidebar icon | | 1024–1280px | 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.*