astro-svelte-frontend

star 0

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.

dedy45 By dedy45 schedule Updated 2/28/2026

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

Install via CLI
npx skills add https://github.com/dedy45/QuantLab --skill astro-svelte-frontend
Repository Details
star Stars 0
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator