Files
ss-tools/.opencode/skills/semantics-svelte/SKILL.md
2026-05-25 13:25:35 +03:00

13 KiB

name: semantics-svelte description: Svelte 5 (Runes) protocol for ss-tools: UX State Machines, Tailwind components, stores, and browser-driven visual validation.

#region Std.Semantics.Svelte [C:5] [TYPE Skill] [SEMANTICS frontend,svelte,ui,ux,tailwind] @BRIEF HOW to build Svelte 5 (Runes) Components for ss-tools with UX State Machines, Tailwind CSS, store topology, and visual-interactive validation. @RELATION DEPENDS_ON -> [Std.Semantics.Core] @RELATION DEPENDS_ON -> [MolecularCoTLogging] @RESTRICTION EXAMPLES ONLY — this file provides language-specific code patterns. All protocol rules (tier definitions, tag catalog, anchor syntax) are defined exclusively in semantics-core. UX contract tags are defined here as examples; the tag catalog lives in semantics-core §III. This file MUST NOT redefine or contradict any rule from semantics-core. @RATIONALE Svelte 5 runes ($state, $derived, $effect, props) chosen for reactive precision and native compiler optimisations over Svelte 4 legacy reactivity (:). Tailwind CSS selected for zero-runtime utility-first styling and rapid visual validation via chrome-devtools MCP. FSM-based UX contracts (@UX_STATE, @UX_FEEDBACK, @UX_RECOVERY) chosen to create verifiable state-transition tests that the browser Judge Agent can execute deterministically. ss-tools internal API wrappers (fetchApi/requestApi) chosen over native fetch to enforce auth, error normalisation, and trace_id propagation. @REJECTED React (JSX) rejected — Svelte's compiler-first approach yields smaller bundles and native reactivity without virtual DOM overhead. Vue rejected — Svelte 5 runes provide simpler mental model. Legacy Svelte 4 syntax (export let, $:, on:event) rejected — incompatible with Svelte 5 runes mode. CSS Modules / styled-components rejected in favour of Tailwind's utility-first approach, which avoids style leakage and simplifies chrome-devtools visual diffing. Native fetch() rejected — bypasses ss-tools middleware chain (auth, trace_id, error normalisation). Plain-text logging rejected per MolecularCoTLogging §VII — JSON lines are mandatory for agent-parsable traces. @INVARIANT Frontend components MUST be verifiable by the browser toolset via chrome-devtools MCP. @INVARIANT Use Tailwind CSS exclusively. Native fetch is forbidden — use requestApi/fetchApi wrappers.

0. SVELTE 5 PARADIGM & UX PHILOSOPHY (SS-TOOLS)

  • STRICT RUNES ONLY: You MUST use Svelte 5 Runes: $state(), $derived(), $effect(), $props(), $bindable().
  • FORBIDDEN SYNTAX: Do NOT use export let, on:event (use onclick), or the legacy $: reactivity.
  • UX AS A STATE MACHINE: Every component is a Finite State Machine (FSM). Declare visual states in the contract BEFORE writing implementation.
  • RESOURCE-CENTRIC: Navigation and actions revolve around Resources (Dashboards, Datasets, Tasks). Every action MUST be traceable.
  • SS-TOOLS SPECIFIC: This is an Apache Superset automation dashboard — components deal with migrations, Git operations, task monitoring, dataset mapping, and plugin management.

I. PROJECT ARCHITECTURAL INVARIANTS (SS-TOOLS)

You are bound by strict repository-level design rules:

  1. Styling: Tailwind CSS utility classes are MANDATORY. Minimize scoped <style>. If custom CSS is absolutely necessary, use @apply directives.
  2. Localization: All user-facing text MUST use the $t store from src/lib/i18n. No hardcoded UI strings.
  3. API Layer: You MUST use the internal fetchApi/requestApi wrappers from $lib/api. Using native fetch() is a fatal violation.
  4. SvelteKit Routing: Pages live under src/routes/. Components live under src/lib/components/. Stores under src/lib/stores/.
  5. Testing: Use Vitest with @testing-library/svelte for component tests.
  6. Component Reuse: Before creating any new component, scan the existing library:
    • Atoms: $lib/ui/Button.svelte, $lib/ui/Select.svelte, $lib/ui/Input.svelte, $lib/ui/Card.svelte
    • Widgets: $lib/components/ui/SearchableMultiSelect.svelte, $lib/components/ui/MultiSelect.svelte
    • Infrastructure: addToast() from $lib/toasts.js (Toast already mounted in root layout)
    • Patterns (no component needed): badges (rounded-full px-2.5 py-0.5 text-xs font-medium), tooltips (native title), skeletons (animate-pulse bg-gray-200), collapsibles (<details><summary>), empty states (border-dashed bg-gray-50), confirmations (confirm()) Refer to .opencode/command/speckit.plan.md §"Frontend Component Reuse Scan" for the mandatory scan workflow.

II. UX CONTRACTS (STRICT UI BEHAVIOR)

Every component MUST define its behavioral contract in the header.

  • @UX_STATE: Maps FSM state names to visual behavior. Example: @UX_STATE Loading -> Spinner visible, btn disabled, aria-busy=true.
  • @UX_FEEDBACK: Defines external system reactions (Toast, Shake, RedBorder, Modal).
  • @UX_RECOVERY: Defines the user's recovery path. Example: @UX_RECOVERY Retry button, Clear filters, Reload page.
  • @UX_REACTIVITY: Explicitly declares the state source. Example: @UX_REACTIVITY: Props -> $props(), LocalState -> $state(...).
  • @UX_TEST: Defines the interaction scenario for the automated Judge Agent. Example: @UX_TEST: Idle -> {click: submit, expected: Loading}.

III. STATE MANAGEMENT & STORE TOPOLOGY (SS-TOOLS STORES)

Key stores in ss-tools:

  • taskDrawerStore — Background task monitoring drawer
  • sidebarStore — Navigation sidebar state
  • authStore — Authentication state (user, roles, permissions)
  • notificationStore — Toast/snackbar notifications
  • dashboardStore — Active dashboard data
  • migrationStore — Migration plan and progress

Store subscription rules:

  • Use the $ prefix for reactive store access (e.g., $sidebarStore.collapsed).
  • Graph Linkage: Whenever a component reads or writes to a global store, declare it: @RELATION BINDS_TO -> [Store_ID]

IV. IMPLEMENTATION & ACCESSIBILITY (A11Y)

  1. Event Handling: Use native attributes (e.g., onclick={handler}, onchange={handler}).
  2. Transitions: Use Svelte's built-in transitions (fade, slide, fly) for UI state changes.
  3. Async Logic: Every async task (API calls) MUST be handled within a try/catch block that:
    • Sets isLoading = $state(true) before the call
    • Catches errors and transitions to Error @UX_STATE
    • Provides @UX_FEEDBACK (Toast notification)
    • Sets isLoading = $state(false) in finally
  4. A11Y: Proper ARIA roles (aria-busy, aria-invalid, aria-describedby). Semantic HTML (<nav>, <main>, <section>). Keyboard navigation for modals and drawers.

V. LOGGING (MOLECULAR-COT FOR UI)

Frontend logging uses log() from $lib/cot-logger, emitting JSON lines per MolecularCoTLogging protocol. Import: import { log } from "$lib/cot-logger";

Marker Reference

Marker When Signature
REASON BEFORE API call or state mutation log("ComponentID", "REASON", "intent", payload)
REFLECT AFTER successful operation (verification) log("ComponentID", "REFLECT", "outcome", payload)
EXPLORE ON error, fallback, or violated assumption log("ComponentID", "EXPLORE", "message", payload, error="...")

Invariants

  • Every log line is a single JSON object — no plain-text prefixes.
  • trace_id propagates from HTTP response headers via the ss-tools API wrappers.
  • One marker per line. No markerless log lines in C4/C5 components.

VI. CANONICAL SVELTE 5 COMPONENT TEMPLATE (SS-TOOLS)

Region format for HTML/Svelte comments:

<!-- #region MigrationTaskCard [C:3] [TYPE Component] [SEMANTICS ui,migration,task] -->
<!-- @BRIEF Card displaying a migration task with status, progress, and action buttons. -->
<!-- @LAYER UI -->
<!-- @RELATION DEPENDS_ON -> [StatusBadge] -->
<!-- @RELATION DEPENDS_ON -> [ProgressBar] -->
<!-- @RELATION BINDS_TO -> [taskDrawerStore] -->
<!-- @RELATION BINDS_TO -> [notificationStore] -->
<!-- @UX_STATE Idle -> Default card view with task summary. -->
<!-- @UX_STATE Loading -> Action button disabled, spinner active, progress bar animated. -->
<!-- @UX_STATE Error -> Red border, error icon, retry button visible. -->
<!-- @UX_STATE Success -> Green border, checkmark, duration displayed. -->
<!-- @UX_FEEDBACK Toast notification on start/fail/complete. -->
<!-- @UX_FEEDBACK Drawer opens on "View Logs" click. -->
<!-- @UX_RECOVERY Retry button on error. Clear/Cancel on running task. -->
<!-- @UX_REACTIVITY Props -> $props(), LocalState -> $state(isLoading, error). -->
<!-- @UX_TEST: Idle -> {click: "Run Migration", expected: Loading -> Success toast}. -->
<script>
  import { fetchApi } from "$lib/api";
  import { log } from "$lib/cot-logger";
  import { t } from "$lib/i18n";
  import { taskDrawerStore } from "$lib/stores";
  import { notificationStore } from "$lib/stores";
  import StatusBadge from "./StatusBadge.svelte";
  import ProgressBar from "./ProgressBar.svelte";

  let { taskId, dashboardName, sourceEnv, targetEnv } = $props();

  let isLoading = $state(false);
  let error = $state(null);
  let status = $state("idle");

  async function handleRunMigration() {
    isLoading = true;
    status = "loading";
    error = null;
    log("MigrationTaskCard", "REASON", "Starting migration", {
      taskId, dashboardName, sourceEnv, targetEnv
    });
    try {
      const result = await fetchApi(`/api/tasks/${taskId}/run`, { method: "POST" });
      status = "success";
      log("MigrationTaskCard", "REFLECT", "Migration completed", { taskId, result });
      notificationStore.add({ type: "success", message: $t("migration.completed", { name: dashboardName }) });
    } catch (e) {
      status = "error";
      error = e.message;
      log("MigrationTaskCard", "EXPLORE", "Migration failed", { taskId }, error = e.message);
      notificationStore.add({ type: "error", message: $t("migration.failed", { name: dashboardName }) });
    } finally {
      isLoading = false;
    }
  }

  function handleViewLogs() {
    log("MigrationTaskCard", "REASON", "Opening task drawer", { taskId });
    taskDrawerStore.open(taskId);
  }
</script>

<div
  class="rounded-lg border p-4 transition-colors {status === 'error' ? 'border-red-500 bg-red-50' : ''} {status === 'success' ? 'border-green-500 bg-green-50' : 'border-gray-200 bg-white'}"
  role="region"
  aria-label={$t("migration.task_card", { name: dashboardName })}
>
  <div class="flex items-center justify-between mb-2">
    <h3 class="font-semibold text-gray-900">{dashboardName}</h3>
    <StatusBadge status={status} />
  </div>

  <div class="text-sm text-gray-500 mb-3">
    {$t("migration.from")}: {sourceEnv} → {$t("migration.to")}: {targetEnv}
  </div>

  {#if status === "loading"}
    <ProgressBar />
  {/if}

  {#if error}
    <p class="text-sm text-red-600 mb-2" aria-live="polite">{error}</p>
  {/if}

  <div class="flex gap-2 mt-3">
    <button
      class="btn-primary text-sm"
      onclick={handleRunMigration}
      disabled={isLoading}
      aria-busy={isLoading}
    >
      {#if isLoading}
        <span class="spinner mr-1" aria-hidden="true"></span>
      {/if}
      {status === "error" ? $t("actions.retry") : $t("actions.run")}
    </button>
    <button class="btn-secondary text-sm" onclick={handleViewLogs}>
      {$t("actions.view_logs")}
    </button>
  </div>
</div>
<!-- #endregion MigrationTaskCard -->

VII. SS-TOOLS TAILWIND CONVENTIONS

Design tokens (from project config)

  • Primary: blue-600 / blue-700 (buttons, links, accents)
  • Success: green-500 / green-600
  • Error: red-500 / red-600
  • Warning: amber-400 / amber-500
  • Background: gray-50 (page), white (cards)
  • Text: gray-900 (primary), gray-500 (muted)

Component class conventions

  • Page layout: max-w-7xl mx-auto px-4 py-6
  • Card: bg-white rounded-lg shadow-sm border border-gray-200 p-4
  • Button primary: bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700 disabled:opacity-50
  • Button secondary: border border-gray-300 text-gray-700 px-4 py-2 rounded-md hover:bg-gray-50
  • Table: min-w-full divide-y divide-gray-200

VIII. VITEST CONVENTIONS

Component tests follow this pattern:

// #region MigrationTaskCardTests [C:1] [TYPE Module]
import { render, screen, fireEvent } from "@testing-library/svelte";
import { describe, it, expect, vi } from "vitest";
import MigrationTaskCard from "./MigrationTaskCard.svelte";

describe("MigrationTaskCard", () => {
  it("renders dashboard name and environments", () => {
    render(MigrationTaskCard, {
      props: { taskId: "1", dashboardName: "Sales", sourceEnv: "dev", targetEnv: "prod" }
    });
    expect(screen.getByText("Sales")).toBeTruthy();
    expect(screen.getByText(/dev.*prod/)).toBeTruthy();
  });

  it("shows loading state when action clicked", async () => {
    // ... button click → loading assertion
  });
});
// #endregion MigrationTaskCardTests

IX. FRONTEND VERIFICATION

# From frontend/ directory
npm run test          # Vitest (unit/component tests)
npm run build         # Production build check
npm run dev           # Development server (for browser validation)

#endregion Std.Semantics.Svelte