Skip to main content

frontend-guide

Read before writing or reviewing any frontend code in lightly_studio_view - Svelte, TypeScript, or SvelteKit files. Covers component structure and naming, stores vs runes, absolute vs relative imports, Shadcn and Tailwind usage, Svelte 5 syntax, TanStack Query hooks, bundle size limits, Storybook stories, and vitest conventions.

Quellinformationen

Repository
lightly-ai/lightly-studio
Letzte Quellaktivität
5. August 2026 um 08:10
Erkannte Sprache von SKILL.md
Englisch
Sterne
896
Forks
40

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
name
frontend-guide
description
Read before writing or reviewing any frontend code in lightly_studio_view - Svelte, TypeScript, or SvelteKit files. Covers component structure and naming, stores vs runes, absolute vs relative imports, Shadcn and Tailwind usage, Svelte 5 syntax, TanStack Query hooks, bundle size limits, Storybook stories, and vitest conventions.
# Frontend coding guidelines Coding standards for frontend development in LightlyStudio using SvelteKit and TypeScript. ## Key Principles - Write concise, technical TypeScript code. Use Svelte-specific features (like runes) only when necessary within Svelte-compiled components. - Keep components under 100 lines. Split code into logical, testable parts. - Embrace TDD. Use [vitest](https://vitest.dev/) for unit and integration tests. - Follow [Svelte](https://svelte.dev/docs) and [SvelteKit](https://kit.svelte.dev/docs) official documentation. ## Framework-agnostic approach Minimize framework-specific syntax to reduce coupling and improve testability: - Prefer [writable](https://svelte.dev/docs/svelte/svelte-store)/readable/derived stores over runes for state management - stores have explicit imports and clearer dependencies. - Use runes only when their specific features are necessary for component-level reactivity. - Less framework coupling means easier maintenance, onboarding, and migration. ## Project structure & naming - **PascalCase** for component names and their folders (e.g., `AuthForm/AuthForm.svelte`). - **camelCase** for non-component files, variables, functions, and props (e.g., `useAuth.ts`, `const myVar`). - Every component and hook lives in its own folder scoping related files together. Canonical layout: ``` src/ components/ AuthForm/ AuthForm.svelte AuthForm.test.ts AuthForm.stories.svelte # if needed UserDashboard/ UserDashboard.svelte UserDashboard.test.ts UserDashboard.helpers.ts # if needed UserProfile/ # subcomponent UserProfile.svelte UserProfile.test.ts lib/ hooks/ index.ts # barrel exports useAuth/ useAuth.ts useAuth.test.ts useData/ useData.ts useData.test.ts ``` - Use `.svelte.ts` files for component logic, state machines, and hooks that use TanStack Query (since v6 uses runes internally). - Use barrel exports (`index.ts`) to define a module's public API. Import from module level, not deep paths: ```typescript // ✅ from "$lib/hooks" - not "$lib/hooks/useData/useData" import { useData, useAuth } from "$lib/hooks"; ``` ## Imports - **Absolute imports** for shared modules: `from "$lib/hooks"`, `from "$lib/components/ui/button"`. - **Relative imports** only within the same module folder: `from "../UserDashboard.helpers"`. ## TypeScript - Use TypeScript for all code. - Define `interface` for component props, function parameters, and return types: ```typescript interface UseDataParams { title: string; onClick: () => void; } interface UseDataReturn { data: string; isLoading: boolean; } export function useData(params: UseDataParams): UseDataReturn { return { data: params.title, isLoading: false }; } ``` - Avoid exporting/importing types. Derive types from source code using utility types to keep things DRY: ```typescript type UseDataParams = Parameters<typeof useData>[0]; type UseDataReturn = ReturnType<typeof useData>; type TitleOnly = Pick<UseDataParams, "title">; type WithoutTitle = Omit<UseDataParams, "title">; ``` ## UI and Styling - **[Shadcn](https://next.shadcn-svelte.com/)** components from `$lib/components/ui` for standard UI elements (buttons, inputs, cards, tabs, alerts, tables). - **Project-specific components** from `$lib/components` when combining multiple Shadcn components, adding business logic, or needing custom behavior. - **[Bits-UI](https://www.bits-ui.com/)** as the base component library underlying Shadcn. - **[Lucide Icons](https://lucide.dev/icons/)** for all icons - import from `@lucide/svelte`. - Use `cn()` from `$lib/utils` for conditional Tailwind class composition. - Prefer explicit props over object spreading - components should receive only the specific props they need. Exception: forwarding HTML attributes via `...rest`, or intentional wrapper/proxy components. ## Performance Keep JS chunk sizes **below 500KB**. Use dynamic imports for heavy components: ```typescript <script lang="ts"> import { onMount } from 'svelte'; let HeavyChart; onMount(async () => { const module = await import('$lib/components/HeavyChart.svelte'); HeavyChart = module.default; }); </script> {#if HeavyChart} <svelte:component this={HeavyChart} /> {/if} ``` When reviewing bundle size: - Check build output for chunk-size warnings. - Identify heavy dependencies before adding them to eagerly loaded routes. - Prefer lazy loading, vendor splitting, or dependency deduplication when a chunk exceeds the limit. ## Svelte 5 syntax Use Svelte 5 patterns in all new code: **Props** - use `$props()` with a typed interface: ```typescript <script lang="ts"> interface Props { value: string; placeholder?: string; onSearch: (query: string) => void; disabled?: boolean; } let { value, placeholder = 'Enter text', onSearch, disabled = false }: Props = $props(); </script> ``` **Event handlers** - use `onclick`, `onchange`, etc. (not `on:click`, `on:change`). **Reactive declarations** - use `$derived` or `derived()` stores, not `$:`. Do not mix the two approaches - pick one per file/module. **Page state** - use `$app/state`, not `$app/stores`: ```typescript import { page } from "$app/state"; page.params.sampleId; // no $ prefix needed ``` **Hooks and reactivity** - for hooks that wrap TanStack Query, accept a **getter function (thunk)** for reactive parameters. TanStack Query v6 uses thunks for reactivity — do not pass Svelte stores or `$derived` values: ```typescript // Hook definition (useFrames.svelte.ts — must be .svelte.ts for TanStack v6) export const useFrames = ( getParams: () => { video_frame_collection_id: string; filter: VideoFrameFilter } ) => { const query = createInfiniteQuery(() => { const { video_frame_collection_id, filter } = getParams(); return { ...getAllFramesInfiniteOptions({ path: { video_frame_collection_id }, body: { filter } }), getNextPageParam: (lastPage) => lastPage.nextCursor || undefined }; }); return { query }; }; // Consumer (+page.svelte) const { query } = useFrames(() => ({ video_frame_collection_id: collectionId, filter: currentFilter })); // query is a reactive proxy — access query.isSuccess, query.data directly (no $ prefix) ``` For non-TanStack hooks with static parameters, pass values via SvelteKit's page load function or direct props. ## State management & hooks Create small, reusable hooks in `src/lib/hooks` - avoid monolithic stores. We do not use a `services` folder; hooks handle data fetching and state. Ref: `lightly_studio_view/src/lib/hooks/useTags/useTags.ts`, `lightly_studio_view/src/lib/hooks/useFeatureFlags/useFeatureFlags.ts`. Generic hooks go in `src/lib/hooks`; component-specific hooks go in the component's folder. For data fetching and API work: - Use [TanStack Query](https://tanstack.com/query/latest/docs/framework/svelte/overview) for all data fetching. TanStack Query v6 is runes-based — hooks that call `createQuery`/`createInfiniteQuery` must be `.svelte.ts` files. - The query result is a **reactive proxy** (not a Svelte store). Access properties like `query.isSuccess`, `query.data` directly — no `$` prefix needed. - Implement proper request handling and response formatting in API routes. **Store-based hook example** ([playground](https://svelte.dev/playground/hello-world?version=5.32.1#H4sIAAAAAAAAE3WRwW6DMBBEf2W1qhSioqS9koBU9TNKD8TZVFZhjex1kwr53yvjhkCVXjisZ2aHtwNy0xEW6B29Gs9CFujSdH1LmONJt-SweBtQvvuoigPMr56Xvt-4L2olzg6No3tzZViIxWGBe6es7gXahj_KGsXVWNUMAKC73liBAWY1Apys6WC12d6GG3GrXc3JpAy76FHxLQfNylJHLBCgnAVl613N-23aXUXz_uBFDINh1Wr1WQ7ZGsrqFpAtw2-vMaKA4WHcEWJO8lY1Y45CF8FCrKeQ_8No0X2J6e_TjNTE42y1NIeWJhqJ69aJsZRQJAxjQSgnw559dyBbZU_rUUWXMfHkWYk2vOADQ_rb9J0kMx5XRcLuWTa-PzZCWaZGdAoe4TkuioJwPdCUZMmRjOvuRTmS1HJhtiTe8p3z5rM4CLuaw_IS7zlKo9uz5iMWp6Z1FH4Aaa7hNOYCAAA=)): ```typescript // useCounter.ts import { writable } from 'svelte/store'; const count = writable<number>(0); export function useCounter() { function increment() { count.update((c) => c + 1); } function resetCount() { count.set(0); } return { count, increment, resetCount }; } // Counter.svelte <script lang="ts"> import { useCounter } from './useCounter'; const { count, increment } = useCounter(); </script> <button onclick={() => increment()}>Count: {$count}</button> ``` ### Avoiding props drilling Prefer these solutions in order: 1. **Svelte Context API** (`setContext`/`getContext`) - for state shared within a component subtree. 2. **Svelte stores** - for truly global state (auth, preferences) shared across unrelated trees. 3. **`$app/state`** - for server-loaded page data accessible by any component on the page. 4. **Svelte 5 Snippets** (`Snippet` type) - for UI composition / layout customization without passing content as props. ## Routing and Pages - Use [SvelteKit's file-based routing](https://svelte.dev/tutorial/kit/layouts) in `src/routes/`. - Use dynamic routes with [slug](https://svelte.dev/tutorial/kit/params) syntax. E.g. sample details at `lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/images/[sampleId]/`. - Use `+layout.svelte` for shared layouts. E.g. the collection layout at `lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/+layout.svelte`. ## Storybook Use simplified story syntax - no explicit `{#snippet children()}` for text content: ```typescript <Story name="H1" args={{ variant: 'h1' }}> Heading 1 - Large Page Title </Story> ``` ## Testing **Testing levels:** Unit tests for isolated components/functions, integration tests for component interactions, end-to-end tests for full application flows. **Unit test example:** ```typescript import { render, screen } from "@testing-library/svelte"; import MyComponent from "./MyComponent.svelte"; describe("MyComponent", () => { it("renders the title", () => { render(MyComponent, { props: { title: "Hello World" } }); expect(screen.getByText("Hello World")).toBeInTheDocument(); }); }); ``` ### Test optimization rules - **Use `defaultProps`** helper objects to avoid repeating prop definitions across tests. Override individual props with spread: `{ ...defaultProps, isUploading: true }`. - **No mirror tests** for simple boolean toggles - testing the truthy case is sufficient when a single boolean controls the state. - **Combine related assertions** into one test rather than creating separate tests for closely related checks (e.g., placeholder + accessible label). - **Test behavior, not implementation** - don't assert on CSS classes, internal structure, or how hooks are called. Assert on user-visible outcomes. - **Remove duplicate tests** that verify the same behavior with different queries. ### Running tests Before submitting code: ```bash # Static checks (TypeScript, ESLint, Prettier, Svelte check) make static-checks # Unit tests npm run test:unit ``` All checks must pass before committing.
Auf GitHub ansehen