frontend-guide
Skill896 starsChanged 31 days agoApache-2.0View on GitHub
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.
We split this skill into the rules it gives an agent
31 rules. About 0 are common, 16 are distinctive, and 15 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈16
Distinctive · estimate
This author's own choices. Worth a look.
15
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 12
Write concise, technical TypeScript code.
- Distinctive: line 13
Keep components under 100 lines.
- Distinctive: line 15
Follow Svelte and SvelteKit official documentation.
- Distinctive: line 21
Prefer writable/readable/derived stores over runes for state management - stores have explicit imports and clearer dependencies.
- Distinctive: line 22
Use runes only when their specific features are necessary for component-level reactivity.
- Distinctive: line 29
Every component and hook lives in its own folder scoping related files together.
- Very specific: line 58
Use .svelte.ts files for component logic, state machines, and hooks that use TanStack Query (since v6 uses runes internally).
- Very specific: line 59
Use barrel exports (index.ts) to define a module's public API.
- Very specific: line 69
Relative imports: only within the same module folder: from "../UserDashboard.helpers".
- Distinctive: line 73
Use TypeScript for all code.
- Distinctive: line 92
Avoid exporting/importing types.
- Very specific: line 107
Use cn() from $lib/utils for conditional Tailwind class composition.
- Very specific: line 108
Prefer explicit props over object spreading - components should receive only the specific props they need.
- Distinctive: line 112
Keep JS chunk sizes below 500KB.
- Distinctive: line 131
When reviewing bundle size:
- Distinctive: line 133
Check build output for chunk-size warnings.
- Distinctive: line 135
Prefer lazy loading, vendor splitting, or dependency deduplication when a chunk exceeds the limit.
- Distinctive: line 139
Use Svelte 5 patterns in all new code:
- Very specific: line 141
Props: - use $props() with a typed interface:
- Very specific: line 161
Event handlers: - use onclick, onchange, etc.
- Very specific: line 163
Reactive declarations: - use $derived or derived() stores, not $:.
- Very specific: line 165
Page state: - use $app/state, not $app/stores:
- Very specific: line 204
Create small, reusable hooks in src/lib/hooks - avoid monolithic stores.
- Very specific: line 211
Use TanStack Query for all data fetching.
- Distinctive: line 246
Prefer these solutions in order:
- Very specific: line 255
Use SvelteKit's file-based routing in src/routes/.
- Very specific: line 256
Use dynamic routes with slug syntax.
- Very specific: line 257
Use +layout.svelte for shared layouts.
- Very specific: line 261
Use simplified story syntax - no explicit {#snippet children()} for text content:
- Distinctive: line 292
Test behavior, not implementation: - don't assert on CSS classes, internal structure, or how hooks are called.
- Distinctive: line 307
All checks must pass before committing.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
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 for unit and integration tests.
- Follow Svelte and SvelteKit official documentation.
Framework-agnostic approach
Minimize framework-specific syntax to reduce coupling and improve testability:
- Prefer writable/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.tsfiles 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:
// ✅ 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
interfacefor component props, function parameters, and return types:
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:
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 components from
$lib/components/uifor standard UI elements (buttons, inputs, cards, tabs, alerts, tables). - Project-specific components from
$lib/componentswhen combining multiple Shadcn components, adding business logic, or needing custom behavior. - Bits-UI as the base component library underlying Shadcn.
- Lucide Icons for all icons - import from
@lucide/svelte. - Use
cn()from$lib/utilsfor 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:
<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:
<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:
import { page } from "$app/state";
page.params.sampleId; // no $ prefix neededHooks 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:
// 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 for all data fetching. TanStack Query v6 is runes-based — hooks that call
createQuery/createInfiniteQuerymust be.svelte.tsfiles. - The query result is a reactive proxy (not a Svelte store). Access properties like
query.isSuccess,query.datadirectly — no$prefix needed. - Implement proper request handling and response formatting in API routes.
Store-based hook example (playground):
// 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:
- Svelte Context API (
setContext/getContext) - for state shared within a component subtree. - Svelte stores - for truly global state (auth, preferences) shared across unrelated trees.
$app/state- for server-loaded page data accessible by any component on the page.- Svelte 5 Snippets (
Snippettype) - for UI composition / layout customization without passing content as props.
Routing and Pages
- Use SvelteKit's file-based routing in
src/routes/. - Use dynamic routes with slug syntax. E.g. sample details at
lightly_studio_view/src/routes/datasets/[dataset_id]/[collection_type]/[collection_id]/images/[sampleId]/. - Use
+layout.sveltefor shared layouts. E.g. the collection layout atlightly_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:
<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:
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
defaultPropshelper 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:
# Static checks (TypeScript, ESLint, Prettier, Svelte check)
make static-checks
# Unit tests
npm run test:unitAll checks must pass before committing.
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- BojanKocijan/bk-charterline★ 4same name
- BojanKocijan/design-forge★ 4same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository8 other files it gives its agents.
- AGENTS.mdAGENTS.md
- CLAUDE.mdCLAUDE.md
- .github/copilot-instructions.mdCopilot instructions
- backend-guideSkill
- best-practicesSkill
- glossarySkill
- pull-requestsSkill
- python-guideSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

