Beetle Frontend Best Practices (Verified)

Authoritative, condensed guidance for Svelte 5 + SvelteKit, Tailwind v4, daisyUI v5, Playwright. Sources cross-checked via MCP: Svelte docs, daisyUI repo/docs, Playwright docs.

Svelte 5 + SvelteKit

  • Runes: Prefer $state, $derived, $effect for state/reactivity.
  • Event syntax: onclick={...} works; on:click remains supported.
  • Build-time guards: Use import { building } from '$app/environment' and gate client-only code (if (!building) ...).
  • Routing & load: Favor server load for SEO and consistent data contracts; use streaming promises for non-critical data.
  • Accessibility: Keep semantic roles and accessible names for Playwright getByRole stability.

Tailwind v4 + daisyUI v5

  • CSS entry:
    @import "tailwindcss";
    /* daisyUI via PostCSS plugin (preferred for v4) */
    @plugin "daisyui" {
      themes:
        light --default,
        dark --prefersdark;
    }
  • Vite: Enable Tailwind plugin
    // vite.config.ts
    import tailwindcss from "@tailwindcss/vite";
    import { sveltekit } from "@sveltejs/kit/vite";
    export default { plugins: [tailwindcss(), sveltekit()] };
  • PostCSS: Optional with Tailwind v4; this repo uses postcss.config.js with @tailwindcss/postcss and autoprefixer which is fully supported.
  • Theme control: Use CSS @plugin "daisyui" { themes: ... } or Tailwind config plugins: [daisyui] (either is fine); minimize theme list for bundle size.
  • A11y: Preserve focus indicators (:focus-visible), use ARIA roles on custom controls, prefer daisyUI components with roles.

Playwright (E2E)

  • Config (CI-friendly)
    import { defineConfig } from "@playwright/test";
    export default defineConfig({
      workers: process.env.CI ? 1 : undefined,
      retries: process.env.CI ? 2 : 0,
      use: {
        trace: "on-first-retry",
        video: "on-first-retry",
        screenshot: "only-on-failure",
        // Decide per suite:
        // serviceWorkers: 'block' | 'allow'
      },
    });
  • Locators: Prefer page.getByRole('button', { name: 'Play' }) and expect(locator).toHaveAccessibleName('Play').
  • Auth: Use worker-scoped storageState fixtures to authenticate once per worker.
  • Network: Use route.fulfill/browserContext.route for API mocking; isolate third-party calls.
  • Tracing: Keep traces/videos for flakies only (on-first-retry).

UI Patterns

  • Theme picker: Use daisyUI theme-controller or data-theme on :root; store last theme in localStorage.
  • Dropdowns/menus: Use daisyUI
    <div class="dropdown">
      <div tabindex="0" role="button">Open</div>
      <ul tabindex="-1" class="dropdown-content menu"></ul>
    </div>
    Keep tabindex + role for keyboard support.
  • Modals: Prefer <dialog class="modal"> patterns; ensure focus trap and close actions.

Data & Performance

  • Load contracts: Normalize API shapes; include error states from server load.
  • Selective loading: Use streaming promises for non-critical data.
  • Images/fonts: Use SvelteKit guidance; avoid waterfalls; preload fonts prudently.

Service Workers

  • Cache busting: Update cache version on deploy; provide clear cache-clear instructions (see guides/CACHE_CLEAR_INSTRUCTIONS.md).
  • Testing: In Playwright, set serviceWorkers per-suite and optionally wait for activation with context.waitForEvent('serviceworker').

— Sources: SvelteKit docs (routing/build/performance/testing), daisyUI v5 docs (themes/components/config), Playwright docs (best practices, retries, tracing, service workers).