13 KiB
#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(useonclick), 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:
- Styling: Tailwind CSS utility classes are MANDATORY. Minimize scoped
<style>. If custom CSS is absolutely necessary, use@applydirectives. - Localization: All user-facing text MUST use the
$tstore fromsrc/lib/i18n. No hardcoded UI strings. - API Layer: You MUST use the internal
fetchApi/requestApiwrappers from$lib/api. Using nativefetch()is a fatal violation. - SvelteKit Routing: Pages live under
src/routes/. Components live undersrc/lib/components/. Stores undersrc/lib/stores/. - Testing: Use Vitest with
@testing-library/sveltefor component tests. - 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 (nativetitle), 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.
- Atoms:
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 drawersidebarStore— Navigation sidebar stateauthStore— Authentication state (user, roles, permissions)notificationStore— Toast/snackbar notificationsdashboardStore— Active dashboard datamigrationStore— 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)
- Event Handling: Use native attributes (e.g.,
onclick={handler},onchange={handler}). - Transitions: Use Svelte's built-in transitions (
fade,slide,fly) for UI state changes. - Async Logic: Every async task (API calls) MUST be handled within a
try/catchblock 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)infinally
- Sets
- 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_idpropagates 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