name: astro-svelte-frontend description: Gunakan skill ini saat bekerja dengan frontend QuantLab (Astro 5, Svelte 5 Runes, shadcn-svelte, SciChart.js, Tailwind v4). Mencakup: tambah komponen, buat halaman baru, integrasi API, desain sistem, dan styling.
Astro + Svelte Frontend Development Skill
Skill ini memandu agent dalam pengembangan frontend QuantLab (frontend/src/). Platform yang digunakan: Astro 5 (SSG/Islands), Svelte 5 (Runes), shadcn-svelte, Tailwind CSS v4, SciChart.js v4.
๐ฏ Kapan Skill Ini Diaktifkan
Aktifkan OTOMATIS jika:
- User minta buat halaman (page) baru
- User minta buat atau modifikasi Svelte component
- Ada build error (
npm run build) - Diskusi tentang styling, design system, atau CSS tokens
- Integrasi antara frontend โ backend API
- WebSocket atau real-time data di frontend
- Deploy ke Cloudflare Pages
- Playwright E2E testing
๐ Struktur Frontend (Wajib Dipahami)
frontend/src/
โ
โโโ pages/ โ Astro pages (routing by file name)
โ โโโ index.astro โ Landing page EN
โ โโโ about.astro โ About page
โ โโโ features.astro โ Features (6 cards + comparison table)
โ โโโ how-it-works.astro โ 3-step guide
โ โโโ pricing.astro โ Tier pricing
โ โโโ demo.astro โ Demo playground (CSV upload)
โ โโโ roadmap.astro โ Public roadmap
โ โโโ changelog.astro โ Version history
โ โโโ blog/ โ Blog (Astro Content Collections)
โ โโโ docs/ โ API documentation
โ โโโ app/ โ Dashboard (AUTH REQUIRED)
โ โ โโโ index.astro โ Main dashboard
โ โ โโโ regime.astro โ Regime Explorer
โ โ โโโ trade-analysis.astro
โ โ โโโ portfolio.astro
โ โ โโโ risk.astro
โ โ โโโ signals.astro
โ โ โโโ strategies/ โ CRUD strategies
โ โ โโโ backtesting/ โ Backtest runner
โ โ โโโ orders/ โ Order management
โ โ โโโ settings/ โ 5 sub-pages
โ โโโ id/ โ Indonesian locale
โ โโโ og/ โ OG image generator
โ
โโโ layouts/
โ โโโ BaseLayout.astro โ Global layout (nav, footer, SEO meta)
โ โโโ DashboardLayout.astro โ Auth-gated layout
โ
โโโ components/
โ โโโ dashboard/ โ Svelte dashboard components
โ โ โโโ KpiCard.svelte
โ โ โโโ KpiRow.svelte โ Wired to real API
โ โ โโโ RegimeSnapshot.svelte โ WS + REST fallback
โ โ โโโ RegimeExplorer.svelte โ Full 4D explorer
โ โ โโโ TradeAnalysisDashboard.svelte
โ โ โโโ EquityCurve.svelte
โ โ โโโ TradeLog.svelte
โ โ โโโ RiskGauge.svelte โ Real VaR data
โ โ โโโ SectorHeatmap.svelte
โ โ โโโ OrderBookDepth.svelte
โ โโโ charts/
โ โ โโโ SciChartEquity.svelte โ WebGL equity curve
โ โ โโโ SciChartHistogram.svelte โ R-distribution
โ โโโ layout/
โ โ โโโ DashboardShell.svelte
โ โ โโโ DashboardHeader.svelte
โ โ โโโ Sidebar.svelte โ Tier-gated nav
โ โโโ shared/
โ โ โโโ AuthForm.svelte
โ โ โโโ LoadingSpinner.svelte
โ โ โโโ ErrorBoundary.svelte
โ โ โโโ EmptyState.svelte
โ โโโ cards/
โ โ โโโ RegimeSummaryCard.svelte โ 4D grid display
โ โโโ landing/
โ โโโ DemoUpload.svelte โ EN/ID locale prop
โ
โโโ lib/
โ โโโ api.ts โ API client (apiFetch + typed endpoints)
โ โโโ supabase.ts โ Auth client
โ โโโ i18n.ts โ EN + ID translations
โ โโโ utils.ts โ cn() utility
โ โโโ plotly-theme.ts โ Chart theme config
โ โโโ stores/
โ โโโ dashboard.ts โ Nav config + types
โ โโโ websocket.svelte.ts โ WS regime stream store
โ
โโโ styles/
โโโ global.css โ Design system tokens SSoT
โโโ blog.css โ Blog prose typography
๐จ Design System (WAJIB Konsisten)
CSS Variables (dari global.css)
/* Colors โ PAKAI INI, jangan hardcode hex */
var(--color-primary) /* accent utama */
var(--color-bg) /* background */
var(--color-surface) /* card background */
var(--color-text) /* body text */
var(--color-muted) /* secondary text */
var(--color-border) /* border/divider */
/* Radius */
var(--radius-sm) /* 4px */
var(--radius-md) /* 8px */
var(--radius-lg) /* 12px */
var(--radius-xl) /* 20px */
/* Regime Colors */
var(--regime-trending-up) /* #22c55e */
var(--regime-trending-down) /* #ef4444 */
var(--regime-ranging) /* #f59e0b */
var(--regime-unknown) /* #6b7280 */
Typography
Gunakan Google Fonts yang sudah di-import: Inter, Roboto Mono (untuk angka/kode)
/* Heading */
font-family: 'Inter', sans-serif;
font-weight: 700;
/* Body */
font-family: 'Inter', sans-serif;
font-weight: 400;
/* Metric numbers */
font-family: 'Roboto Mono', monospace;
๐๏ธ Workflow: Buat Halaman Baru
A. Halaman Publik (Static, Zero JS)
---
// src/pages/new-page.astro
import BaseLayout from '@/layouts/BaseLayout.astro';
const title = "New Page | QuantLab";
const description = "Description for SEO (150-160 chars)";
---
<BaseLayout title={title} description={description}>
<!--
ATURAN halaman publik:
- Gunakan semantic HTML: <main>, <section>, <article>
- Zero JavaScript (tidak ada Svelte component dengan client:*)
- Mobile-first responsive
- Tidak ada emoji di text (professionalism)
-->
<main>
<section class="hero">
<h1>Page Headline</h1> <!-- Satu h1 per halaman! -->
</section>
<section>
<h2>Section Title</h2>
<!-- content -->
</section>
</main>
</BaseLayout>
SEO Checklist untuk halaman baru:
[ ] <title> unik dan deskriptif (50-60 chars)
[ ] <meta description> 150-160 chars
[ ] Satu <h1> per halaman
[ ] hreflang jika ada versi /id/
[ ] Masuk sitemap (di astro.config.mjs filter)
[ ] Tidak ada route di robots.txt Disallow
B. Halaman Dashboard (Auth-Required + Svelte)
---
// src/pages/app/new-feature.astro
import DashboardLayout from '@/layouts/DashboardLayout.astro';
import NewFeatureComponent from '@/components/dashboard/NewFeatureComponent.svelte';
---
<DashboardLayout title="New Feature | QuantLab Dashboard">
<NewFeatureComponent client:idle />
<!--
client:idle โ hydrate saat browser idle (PREFERRED untuk non-critical)
client:load โ hydrate segera (untuk critical above-fold)
client:visible โ hydrate saat enter viewport (untuk below-fold)
-->
</DashboardLayout>
๐ Workflow: Buat Svelte Component dengan Real API
Template Svelte 5 (Runes) Standard
<script lang="ts">
// 1. IMPORTS
import { onDestroy } from 'svelte';
import LoadingSpinner from '@/components/shared/LoadingSpinner.svelte';
import EmptyState from '@/components/shared/EmptyState.svelte';
import { fetchNewData, type NewDataResponse } from '@/lib/api';
// 2. PROPS
interface Props {
asset?: string;
timeframe?: string;
}
let { asset = 'BTCUSDT', timeframe = 'D1' }: Props = $props();
// 3. STATE (Svelte 5 Runes)
let data = $state<NewDataResponse | null>(null);
let loading = $state(true);
let error = $state<string | null>(null);
let initialized = $state(false);
// 4. FETCH LOGIC
async function loadData() {
loading = true;
error = null;
try {
data = await fetchNewData(asset, timeframe);
} catch (err) {
error = err instanceof Error ? err.message : 'Failed to load data';
console.error('NewFeatureComponent:', err);
} finally {
loading = false;
}
}
// 5. REACTIVE EFFECT
$effect(() => {
if (initialized) return; // one-shot guard
initialized = true;
loadData();
});
// 6. REACTIVE REFRESH (saat asset/timeframe berubah)
$effect(() => {
// reactive ke asset + timeframe changes
void asset;
void timeframe;
if (initialized) loadData();
});
// 7. CLEANUP
onDestroy(() => {
// cleanup intervals, sockets, dll
});
</script>
<!-- 8. TEMPLATE -->
{#if loading}
<LoadingSpinner />
{:else if error}
<div class="error-state">
<p>{error}</p>
<button onclick={loadData}>Retry</button>
</div>
{:else if !data}
<EmptyState message="No data available" />
{:else}
<div class="feature-container">
<!-- content dengan data -->
<h3>{data.title}</h3>
<p class="metric">{data.value.toFixed(4)}</p>
</div>
{/if}
<style>
.feature-container {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: 1.5rem;
border: 1px solid var(--color-border);
}
.metric {
font-family: 'Roboto Mono', monospace;
font-size: 2rem;
color: var(--color-primary);
}
.error-state {
color: var(--regime-trending-down);
padding: 1rem;
text-align: center;
}
</style>
๐ Workflow: Tambah API Endpoint ke Frontend
Edit lib/api.ts
// 1. Tambah tipe response
export interface NewFeatureData {
value: number;
signal: string;
confidence: number;
timestamp: string;
}
// 2. Tambah fungsi fetch
export async function fetchNewFeature(
asset: string,
timeframe: string
): Promise<NewFeatureData> {
return apiFetch<NewFeatureData>(`/v1/new-feature/${asset}/${timeframe}`);
}
// 3. Tambah ke SWR cache jika perlu
const NEW_FEATURE_CACHE_TTL = 60_000; // 60 detik
const newFeatureCache = new Map<string, { data: NewFeatureData; expiresAt: number }>();
export async function fetchNewFeatureCached(
asset: string,
timeframe: string
): Promise<NewFeatureData> {
const key = `${asset}:${timeframe}`;
const cached = newFeatureCache.get(key);
if (cached && cached.expiresAt > Date.now()) {
return cached.data; // cache hit
}
const data = await fetchNewFeature(asset, timeframe);
newFeatureCache.set(key, { data, expiresAt: Date.now() + NEW_FEATURE_CACHE_TTL });
return data;
}
โก SciChart.js โ Cara Pakai yang Benar
SciChart.js adalah WebGL + WASM library. Ada aturan khusus:
DO โ
<script>
import { onMount, onDestroy } from 'svelte';
let sciChartSurface: SciChartSurface;
onMount(async () => {
const { SciChartSurface, NumericAxis } = await import('scichart');
try {
const { sciChartSurface: surface, wasmContext } =
await SciChartSurface.create('chart-container');
sciChartSurface = surface;
// setup chart...
} catch (e) {
console.error('SciChart failed, using SVG fallback');
useSVGFallback = true;
}
});
onDestroy(() => {
sciChartSurface?.delete(); // WAJIB cleanup!
});
</script>
DON'T โ
<!-- JANGAN import SciChart secara statis -->
import { SciChartSurface } from 'scichart'; // โ breaks SSR!
<!-- JANGAN lupa delete surface -->
onDestroy(() => {
// missing sciChartSurface.delete() โ WASM memory leak!
});
SVG Fallback Pattern (Best Practice)
{#if useSciChart && sciChartReady}
<div id="scichart-{chartId}" style="height: 400px"></div>
{:else}
<!-- SVG fallback: lebih ringan, no WASM -->
<svg viewBox="0 0 800 400">
{#each dataPoints as point, i}
<circle cx={i * 8} cy={400 - point.y * 4} r="2" fill="var(--color-primary)" />
{/each}
</svg>
{/if}
๐ Internationalization (i18n)
QuantLab support 2 bahasa: EN (default) + ID (Indonesian).
Struktur i18n
// lib/i18n.ts โ tambahkan key baru di sini
export const translations = {
en: {
nav: { ... },
pages: {
newPage: {
title: "New Page",
description: "Description in English"
}
}
},
id: {
nav: { ... },
pages: {
newPage: {
title: "Halaman Baru",
description: "Deskripsi dalam Bahasa Indonesia"
}
}
}
}
Route Convention
/new-page โ English (default)
/id/new-page โ Indonesian
src/pages/new-page.astro
src/pages/id/new-page.astro
๐งช Testing dengan Playwright
Tambah Test untuk Halaman Baru
// tests/new-page.spec.ts
import { test, expect } from '@playwright/test';
test.describe('New Page', () => {
test('loads successfully', async ({ page }) => {
await page.goto('/new-page');
await expect(page).toHaveTitle(/New Page.*QuantLab/);
});
test('has correct h1', async ({ page }) => {
await page.goto('/new-page');
await expect(page.locator('h1')).toContainText('Expected Heading');
});
test('SEO meta description', async ({ page }) => {
await page.goto('/new-page');
const meta = page.locator('meta[name="description"]');
await expect(meta).toHaveAttribute('content', /.{50,}/); // min 50 chars
});
test('mobile responsive 375px', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto('/new-page');
// tidak ada horizontal scroll
const bodyWidth = await page.evaluate(() => document.body.scrollWidth);
expect(bodyWidth).toBeLessThanOrEqual(375);
});
test('returns HTTP 200', async ({ request }) => {
const response = await request.get('/new-page');
expect(response.status()).toBe(200);
});
});
Jalankan Test
cd frontend
# Test semua browser
npx playwright test
# Test file spesifik
npx playwright test tests/new-page.spec.ts
# Test satu browser
npx playwright test --project=chromium
# UI mode (interactive)
npx playwright test --ui
โ Checklist Sebelum Commit Frontend
[ ] npm run build โ 0 errors, 0 warnings
[ ] npm audit โ 0 vulnerabilities
[ ] playwright test โ semua PASS
[ ] Halaman baru punya <title> dan <meta description>
[ ] Mobile responsive di 375px
[ ] Zero JS untuk halaman publik (kecuali island yang diperlukan)
[ ] Svelte component punya: loading state, error state, empty state
[ ] API calls gunakan try/catch dengan error message yang berguna
[ ] Cleanup di onDestroy (intervals, sockets, SciChart surfaces)
[ ] CSS menggunakan CSS variables, bukan hardcode hex
[ ] Tidak ada emoji di halaman publik
๐ Deploy ke Cloudflare Pages
Environment Variables yang Perlu Di-set
Di Cloudflare Pages dashboard โ Settings โ Environment Variables:
PUBLIC_SUPABASE_URL = https://supabase.bamsbung.com
PUBLIC_SUPABASE_ANON_KEY = eyJ... (200+ chars, BUKAN placeholder!)
PUBLIC_API_BASE = https://[lightsail-ip]/v1
Troubleshoot Build Error CF Pages
# Test build lokal dulu
npm run build
# Jika error "supabase is not configured":
# Pastikan ANON_KEY panjangnya > 100 chars
# Placeholder tidak diterima oleh isValidAnonKey()
# Jika error import SciChart:
# Pastikan SciChart hanya di-import secara dynamic (async import())
# Bukan static import di top-level
๐ Reference
- Astro docs: https://docs.astro.build
- Svelte 5 Runes: https://svelte.dev/docs/svelte/$state
- shadcn-svelte: https://www.shadcn-svelte.com
- SciChart.js: https://www.scichart.com/documentation
- Frontend Design System:
frontend/Docs/FRONTEND_DESIGN_SYSTEM.md - Frontend Conventions:
frontend/Docs/ASTRO_SVELTE_CONVENTIONS.md - API client:
frontend/src/lib/api.ts - Design tokens SSoT:
frontend/src/styles/global.css