Web interface for the OpenShock ecosystem — a free and open-source platform for controlling shock collars over the internet. Built with Svelte 5 (runes), SvelteKit 2, TypeScript (strict), Tailwind CSS v4, and Vite.
pnpm run dev # Start dev server (requires hosts file setup for local.openshock.app)
pnpm run build # Production build
pnpm run check # svelte-kit sync + svelte-check (type checking)
pnpm run lint # Prettier check + ESLint
pnpm run format # Auto-format with Prettier
pnpm run test:unit # Vitest unit tests
pnpm run test:e2e # Playwright E2E tests
pnpm run regen-api # Regenerate OpenAPI TypeScript clients (requires Java)Always use pnpm instead of npm or yarn. This also goes for npx, use pnpx.
src/
├── lib/
│ ├── api/internal/{v1,v2}/ # AUTO-GENERATED — never edit manually
│ ├── api/next/ # API helper utilities
│ ├── components/ui/ # shadcn-svelte components — regenerate, don't hand-edit
│ ├── components/ # Custom domain components
│ ├── stores/ # Svelte writable stores (global state)
│ ├── state/ # Svelte 5 reactive state (.svelte.ts)
│ ├── signalr/ # SignalR WebSocket hub handlers
│ ├── types/ # Manual TypeScript types
│ ├── typeguards/ # Runtime type predicates
│ ├── utils/ # Pure utility functions
│ ├── errorhandling/ # API error handling (RFC 7807 Problem Details)
│ ├── inputvalidation/ # Form input validators
│ ├── hooks/ # Custom Svelte hooks
│ └── constants/ # Global constants
├── routes/
│ ├── (anonymous)/ # Public routes (login, signup, etc.)
│ ├── (authenticated)/ # Auth-protected routes (home, settings, admin, etc.)
│ ├── shares/ # Public share views
│ └── flashtool/ # ESP32 firmware flashing tool
├── params/ # SvelteKit route param matchers (guid, integer)
├── app.css # Tailwind v4 theme (OKLch colors, light/dark mode)
└── app.d.ts # Global type declarations
src/lib/api/internal/— Auto-generated by OpenAPI Generator from backend swagger specs. Regenerate withpnpm run regen-api.src/lib/components/ui/— shadcn-svelte components. Add/update via the shadcn CLI, not by hand.
Both directories are excluded from ESLint.
SvelteKit file-based routing with route groups:
(anonymous)— No auth required (login, signup, activate, forgot-password, oauth)(authenticated)— Auth required (home, profile, settings, hubs, shockers, shares, admin)- Route param matchers in
src/params/:[id=guid],[n=integer]
Three tiers:
- Stores (
lib/stores/) — Sveltewritable()stores for global shared state (user, hubs, shares, theme, etc.) - Reactive state (
lib/state/) — Svelte 5$stateobjects (.svelte.tsfiles) for backend metadata, breadcrumbs - SignalR — Real-time WebSocket updates for device status, control logs, OTA progress
- Backend base URL from
PUBLIC_BACKEND_API_URLenv var - Cookie-based auth (
credentials: 'include') - Dual API versions: v1 and v2
- Generated TypeScript Fetch clients in
src/lib/api/internal/{v1,v2}/ - Error responses follow RFC 7807 Problem Details
- Hub endpoint:
1/hubs/user - WebSocket-only transport
- Auto-reconnection: 0ms, 1s, 2s, 5s, 10s, 10s, 15s, 30s, 60s
- Events: DeviceStatus, DeviceUpdate, Log, OTA install lifecycle
This project uses Svelte 5 with runes enabled and modern AST. Use the new APIs:
<script lang="ts">
import type { Snippet } from 'svelte';
interface Props {
title: string;
children?: Snippet;
}
let { title, children }: Props = $props();
let count = $state(0);
let doubled = $derived(count * 2);
$effect(() => {
/* side effects */
});
</script>$props()for component props (notexport let)$state()for reactive state (notletwith reactive assignments)$derived()for computed values (not$:)$effect()for side effectsSnippetfor children/slots (not<slot>)
- Strict mode enabled
- Use
import type { ... }for type-only imports $lib/*alias for the lib directory$app/*for SvelteKit app exports$env/static/publicfor public env vars
- Tailwind CSS v4 with utility classes
cn()helper (from$lib/utils/shadcn) for conditional class merging (clsx + tailwind-merge)tailwind-variantsfor component variant definitions- OKLch color space for theme variables
- Light/dark mode via CSS custom properties
- Prettier: 2-space indent, single quotes, trailing commas (es5), 100 char print width, semicolons
- Prettier plugins: svelte, tailwindcss (class sorting), organize-imports
- ESLint: Flat config with typescript-eslint + eslint-plugin-svelte + prettier
- Trunk-based development with feature branches
- Branch names:
feature/name,bugfix/name,chore/name,hotfix/name,docs/name - PRs squash-merged into
master/develop - Package manager: pnpm (use
pnpm, not npm/yarn)
All public env vars use the PUBLIC_ prefix (SvelteKit convention). Key vars:
PUBLIC_SITE_URL— Base site URL (also determines SvelteKit base path)PUBLIC_BACKEND_API_URL— API server URLPUBLIC_GATEWAY_CSP_WILDCARD— CSP connect-src wildcard for gatewayPUBLIC_DEVELOPMENT_BANNER— Show development warning bannerPUBLIC_TURNSTILE_DEV_BYPASS_VALUE— Cloudflare Turnstile captcha bypass for dev
- Cloudflare Pages when
CF_PAGES=1(uses@sveltejs/adapter-cloudflare) - Node.js otherwise (uses
@sveltejs/adapter-node) - Docker image:
ghcr.io/openshock/frontend - CI: GitHub Actions — build, type check, unit test, lint, CodeQL scanning
You have access to the Svelte MCP server with comprehensive Svelte 5 and SvelteKit documentation.
Use this FIRST to discover all available documentation sections. Returns a structured list with titles, use_cases, and paths. When asked about Svelte or SvelteKit topics, ALWAYS use this tool at the start of the chat to find relevant sections.
Retrieves full documentation content for specific sections. Accepts single or multiple sections. After calling the list-sections tool, you MUST analyze the returned documentation sections (especially the use_cases field) and then use the get-documentation tool to fetch ALL documentation sections that are relevant for the user's task.
Analyzes Svelte code and returns issues and suggestions. You MUST use this tool whenever writing Svelte code before sending it to the user. Keep calling it until no issues or suggestions are returned.
Generates a Svelte Playground link with the provided code. After completing the code, ask the user if they want a playground link. Only call this tool after user confirmation and NEVER if code was written to files in their project.