ima2-gen 2.0.15 → 2.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -0
- package/bin/commands/skill.js +336 -17
- package/bin/ima2.js +22 -2
- package/bin/lib/doctor-checks.js +6 -4
- package/config.js +1 -0
- package/docs/CLI.md +25 -3
- package/docs/PROMPT_STUDIO.ko.md +29 -0
- package/docs/PROMPT_STUDIO.md +31 -0
- package/docs/migration/runtime-test-inventory.md +7 -1
- package/lib/agentImageVideoGen.js +8 -0
- package/lib/agentQueueStore.js +36 -10
- package/lib/agentQueueWorker.js +79 -7
- package/lib/agentRuntime.js +2 -0
- package/lib/assetLifecycle.js +40 -8
- package/lib/canvasVersionStore.js +86 -0
- package/lib/cardNewsGenerator.js +37 -3
- package/lib/cardNewsJobStore.js +10 -0
- package/lib/cardNewsManifestStore.js +6 -12
- package/lib/cardNewsPath.js +25 -0
- package/lib/db.js +2 -0
- package/lib/generatePipeline.js +474 -0
- package/lib/generationInputValidation.js +32 -0
- package/lib/grokImageCore.js +18 -1
- package/lib/historyIndex.js +11 -4
- package/lib/historyList.js +3 -0
- package/lib/imageMetadata.js +3 -0
- package/lib/inflight.js +30 -3
- package/lib/multimodePipeline.js +462 -0
- package/lib/nodeGeneration.js +440 -0
- package/lib/nodeValidation.js +23 -0
- package/lib/oauthProxy/generators.js +3 -246
- package/lib/oauthProxy/multimodeGenerators.js +251 -0
- package/lib/routeHelpers.js +9 -2
- package/package.json +2 -2
- package/routes/agent.js +4 -4
- package/routes/canvasVersions.js +21 -1
- package/routes/cardNews.js +2 -1
- package/routes/edit.js +25 -9
- package/routes/events.js +5 -1
- package/routes/generate.js +2 -481
- package/routes/keys.js +31 -39
- package/routes/multimode.js +2 -502
- package/routes/nodes.js +3 -448
- package/routes/video.js +12 -3
- package/routes/videoExtended.js +19 -4
- package/server.js +53 -1
- package/skills/ima2/SKILL.md +204 -0
- package/skills/ima2-front/SKILL.md +611 -0
- package/skills/ima2-front/references/a11y-patterns.md +134 -0
- package/skills/ima2-front/references/aesthetics.md +327 -0
- package/skills/ima2-front/references/anti-slop.md +241 -0
- package/skills/ima2-front/references/asset-requirements.md +431 -0
- package/skills/ima2-front/references/brand-asset-sourcing.md +122 -0
- package/skills/ima2-front/references/color-system.md +132 -0
- package/skills/ima2-front/references/consistency-locks.md +20 -0
- package/skills/ima2-front/references/crud-ui.md +53 -0
- package/skills/ima2-front/references/dropdown-layer.md +110 -0
- package/skills/ima2-front/references/i18n-global.md +104 -0
- package/skills/ima2-front/references/iterative-design.md +90 -0
- package/skills/ima2-front/references/korea-2026.md +174 -0
- package/skills/ima2-front/references/layout-discipline.md +161 -0
- package/skills/ima2-front/references/liquid-glass.md +181 -0
- package/skills/ima2-front/references/logo-sections.md +194 -0
- package/skills/ima2-front/references/mobile-ux.md +144 -0
- package/skills/ima2-front/references/performance-budget.md +106 -0
- package/skills/ima2-front/references/preflight-full.md +58 -0
- package/skills/ima2-front/references/product-density.md +53 -0
- package/skills/ima2-front/references/prototype-variants.md +54 -0
- package/skills/ima2-front/references/reference-capture.md +120 -0
- package/skills/ima2-front/references/responsive-viewport.md +139 -0
- package/skills/ima2-front/references/seo-baseline.md +123 -0
- package/skills/ima2-front/references/soft-3d-asset-gates.md +70 -0
- package/skills/ima2-front/references/stacks/astro.md +204 -0
- package/skills/ima2-front/references/stacks/mobile-native.md +281 -0
- package/skills/ima2-front/references/stacks/nextjs.md +219 -0
- package/skills/ima2-front/references/stacks/react.md +264 -0
- package/skills/ima2-front/references/stacks/svelte.md +235 -0
- package/skills/ima2-front/references/stacks/vanilla.md +226 -0
- package/skills/ima2-front/references/theme-switching.md +82 -0
- package/skills/ima2-front/references/top-bar.md +126 -0
- package/skills/ima2-front/references/typography-wrapping.md +270 -0
- package/skills/ima2-front/references/ux-writing-ko.md +62 -0
- package/skills/ima2-front/references/visual-verification.md +102 -0
- package/skills/ima2-uiux/SKILL.md +477 -0
- package/skills/ima2-uiux/references/anti-slop-judgment.md +39 -0
- package/skills/ima2-uiux/references/color-system.md +80 -0
- package/skills/ima2-uiux/references/design-isms.md +194 -0
- package/skills/ima2-uiux/references/design-read-example.md +33 -0
- package/skills/ima2-uiux/references/design-system-bootstrap.md +133 -0
- package/skills/ima2-uiux/references/favicon-logo.md +355 -0
- package/skills/ima2-uiux/references/form-patterns.md +118 -0
- package/skills/ima2-uiux/references/intent-discovery-ladder.md +86 -0
- package/skills/ima2-uiux/references/korean-design-vocabulary.md +52 -0
- package/skills/ima2-uiux/references/layout-macrostructures.md +54 -0
- package/skills/ima2-uiux/references/logo-trust-sections.md +96 -0
- package/skills/ima2-uiux/references/mobile-native-ux.md +207 -0
- package/skills/ima2-uiux/references/product-personalities.md +118 -0
- package/skills/ima2-uiux/references/responsive-nav.md +66 -0
- package/skills/ima2-uiux/references/typography-line-breaks.md +193 -0
- package/skills/ima2-uiux/references/ux-preflight.md +65 -0
- package/skills/ima2-uiux/references/ux-states.md +197 -0
- package/skills/ima2-uiux/references/visual-hierarchy.md +115 -0
- package/ui/dist/.vite/manifest.json +13 -13
- package/ui/dist/assets/AgentWorkspace-CtYt4SF5.js +3 -0
- package/ui/dist/assets/{CardNewsWorkspace-Cdxn1ohU.js → CardNewsWorkspace-DN5cVqG5.js} +1 -1
- package/ui/dist/assets/GenerationRequestLogPanel-DnmUQdKR.js +1 -0
- package/ui/dist/assets/{NodeCanvas-C67sLboA.js → NodeCanvas-CxpfIBI0.js} +1 -1
- package/ui/dist/assets/PromptBuilderPanel-C2R3ObtR.js +2 -0
- package/ui/dist/assets/{PromptImportDialog-DeQXsEEv.js → PromptImportDialog-BIxz_yEx.js} +2 -2
- package/ui/dist/assets/PromptImportDiscoverySection-BQTlPMME.js +1 -0
- package/ui/dist/assets/PromptImportFolderSection-Ds_Th9O3.js +1 -0
- package/ui/dist/assets/{PromptLibraryPanel-Db3xEPM_.js → PromptLibraryPanel-Rs4l45pV.js} +2 -2
- package/ui/dist/assets/{SettingsWorkspace-BDk_1W-V.js → SettingsWorkspace-p1AZ6uT9.js} +1 -1
- package/ui/dist/assets/index-0-_vgFGs.js +4 -0
- package/ui/dist/assets/index-Dm3pFxV4.js +23 -0
- package/ui/dist/assets/index-J8yDF3Ch.css +1 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AgentWorkspace-DAg_7zpY.js +0 -3
- package/ui/dist/assets/GenerationRequestLogPanel-C_qvEUi2.js +0 -1
- package/ui/dist/assets/PromptBuilderPanel-DjvVdAie.js +0 -2
- package/ui/dist/assets/PromptImportDiscoverySection-CrwyUKYI.js +0 -1
- package/ui/dist/assets/PromptImportFolderSection-CToGMqYE.js +0 -1
- package/ui/dist/assets/index-2oG6in1i.css +0 -1
- package/ui/dist/assets/index-CoxMOXAU.js +0 -23
- package/ui/dist/assets/index-p8egnGHk.js +0 -4
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# React Stack — Development Rules
|
|
2
|
+
|
|
3
|
+
Rules specific to React projects. Read `core/aesthetics.md`, `core/anti-slop.md`, and `core/visual-verification.md` first.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Domain Routing
|
|
8
|
+
|
|
9
|
+
Before choosing component libraries, read the core references that match the surface:
|
|
10
|
+
|
|
11
|
+
- Korean-first UI → `korea-2026.md`, `ux-writing-ko.md`
|
|
12
|
+
- Tool/dashboard → `product-density.md`
|
|
13
|
+
- Visual/product surface → `asset-requirements.md`
|
|
14
|
+
- Soft 3D/mascot/miniature visuals → `soft-3d-asset-gates.md`
|
|
15
|
+
- Substantial UI change → `visual-verification.md`
|
|
16
|
+
|
|
17
|
+
## Behavior-First Components
|
|
18
|
+
|
|
19
|
+
Prefer proven behavior primitives for complex interactive components:
|
|
20
|
+
|
|
21
|
+
- Radix / Headless UI for dialog, popover, menu, tabs, combobox-like behavior
|
|
22
|
+
- The project dropdown design layer (`references/core/dropdown-layer.md`
|
|
23
|
+
FE-DROPDOWN-LAYER-01) supplies the SKIN over these primitives; the skin never
|
|
24
|
+
replaces the primitive
|
|
25
|
+
- shadcn/ui as source code scaffolding, not as a final visual design
|
|
26
|
+
- Existing repo components before adding a new dependency
|
|
27
|
+
|
|
28
|
+
Customize tokens, radius, shadows, density, and typography. Do not ship default shadcn visuals.
|
|
29
|
+
|
|
30
|
+
## Korean Mobile Patterns
|
|
31
|
+
|
|
32
|
+
React apps targeting Korean mobile flows should support:
|
|
33
|
+
|
|
34
|
+
- bottom sheets for lightweight choices
|
|
35
|
+
- full-screen funnels for complex steps
|
|
36
|
+
- sticky bottom action bars
|
|
37
|
+
- snackbar/toast for reversible confirmations
|
|
38
|
+
- safe-area insets
|
|
39
|
+
- `100dvh` instead of `100vh`
|
|
40
|
+
- long Korean label stress tests
|
|
41
|
+
|
|
42
|
+
## AI Tool States
|
|
43
|
+
|
|
44
|
+
AI features must expose state clearly:
|
|
45
|
+
|
|
46
|
+
- pending / queued
|
|
47
|
+
- streaming or partial result
|
|
48
|
+
- cancel
|
|
49
|
+
- retry
|
|
50
|
+
- undo or revert
|
|
51
|
+
- source/provenance when applicable
|
|
52
|
+
- permission boundary before sensitive actions
|
|
53
|
+
- rate limit or failure recovery
|
|
54
|
+
|
|
55
|
+
Do not hide AI uncertainty behind decorative gradients.
|
|
56
|
+
|
|
57
|
+
## Architecture
|
|
58
|
+
|
|
59
|
+
### Server Components When Supported By The Framework
|
|
60
|
+
Use Server Components only in Next App Router or another RSC-enabled framework. In Vite, SPA-only React, React Native Web, or client-only embeds, do not introduce RSC assumptions or `'use client'` boundaries.
|
|
61
|
+
|
|
62
|
+
When the framework supports RSC, default non-interactive route content to Server Components. Add `'use client'` ONLY when you need:
|
|
63
|
+
- Event handlers (`onClick`, `onChange`)
|
|
64
|
+
- State (`useState`, `useReducer`)
|
|
65
|
+
- Effects (`useEffect`)
|
|
66
|
+
- Browser APIs (`window`, `document`)
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
// Server Component (default) — no 'use client'
|
|
70
|
+
async function ProductPage({ params }) {
|
|
71
|
+
const product = await getProduct(params.id);
|
|
72
|
+
return (
|
|
73
|
+
<div>
|
|
74
|
+
<h1>{product.name}</h1>
|
|
75
|
+
<AddToCartButton productId={product.id} />
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Client Component — only what MUST be interactive
|
|
81
|
+
'use client';
|
|
82
|
+
function AddToCartButton({ productId }) {
|
|
83
|
+
const [adding, setAdding] = useState(false);
|
|
84
|
+
return <button onClick={() => addToCart(productId)}>Add</button>;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### RSC Safety Rules
|
|
89
|
+
- Global state ONLY in Client Components
|
|
90
|
+
- In Next.js, wrap providers in a `'use client'` wrapper component
|
|
91
|
+
- If motion/interactivity needed, extract the interactive part as an **isolated leaf** Client Component
|
|
92
|
+
|
|
93
|
+
### Interactivity Isolation
|
|
94
|
+
Any component using `useMotionValue`, `useTransform`, or perpetual animations MUST be extracted as a dedicated Client Component in RSC-enabled frameworks. Server Components render static layouts only.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## State Management
|
|
99
|
+
|
|
100
|
+
| Need | Solution |
|
|
101
|
+
| ------------------- | ------------------------------------------------------- |
|
|
102
|
+
| Local UI state | `useState` / `useReducer` |
|
|
103
|
+
| Form state | React Hook Form + Zod validation |
|
|
104
|
+
| Server-fetched data | TanStack Query (`@tanstack/react-query`) |
|
|
105
|
+
| Global shared state | Zustand (minimal, flat store) |
|
|
106
|
+
| Deep prop drilling | React Context (for dependency injection, NOT data flow) |
|
|
107
|
+
|
|
108
|
+
**Anti-patterns**:
|
|
109
|
+
- Don't use Context for frequently changing data (causes subtree re-renders)
|
|
110
|
+
- Don't reach for global state when local state suffices
|
|
111
|
+
- Don't use `useEffect` for data fetching — use TanStack Query or Server Components
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Component Patterns
|
|
116
|
+
|
|
117
|
+
### Compound Components
|
|
118
|
+
```tsx
|
|
119
|
+
const Tabs = ({ children }) => {
|
|
120
|
+
const [active, setActive] = useState(0);
|
|
121
|
+
return (
|
|
122
|
+
<TabsContext.Provider value={{ active, setActive }}>
|
|
123
|
+
{children}
|
|
124
|
+
</TabsContext.Provider>
|
|
125
|
+
);
|
|
126
|
+
};
|
|
127
|
+
Tabs.List = TabList;
|
|
128
|
+
Tabs.Panel = TabPanel;
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Custom Hooks (extract reusable logic)
|
|
132
|
+
```tsx
|
|
133
|
+
function useDebounce<T>(value: T, delay = 500): T {
|
|
134
|
+
const [debounced, setDebounced] = useState(value);
|
|
135
|
+
useEffect(() => {
|
|
136
|
+
const timer = setTimeout(() => setDebounced(value), delay);
|
|
137
|
+
return () => clearTimeout(timer);
|
|
138
|
+
}, [value, delay]);
|
|
139
|
+
return debounced;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Generic List Component
|
|
144
|
+
```tsx
|
|
145
|
+
interface ListProps<T> {
|
|
146
|
+
items: T[];
|
|
147
|
+
renderItem: (item: T) => React.ReactNode;
|
|
148
|
+
}
|
|
149
|
+
function List<T>({ items, renderItem }: ListProps<T>) {
|
|
150
|
+
return <ul>{items.map(renderItem)}</ul>;
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## Styling
|
|
157
|
+
|
|
158
|
+
### Tailwind CSS
|
|
159
|
+
- Check `package.json` for Tailwind version. Do NOT mix v3/v4 syntax.
|
|
160
|
+
- **T4 CONFIG GUARD**: For v4, do NOT use `tailwindcss` plugin in `postcss.config.js`. Use `@tailwindcss/postcss` or Vite plugin.
|
|
161
|
+
- Use `cn()` utility for conditional classes:
|
|
162
|
+
```tsx
|
|
163
|
+
import { cn } from '@/lib/utils';
|
|
164
|
+
<button className={cn(
|
|
165
|
+
'px-4 py-2 rounded',
|
|
166
|
+
variant === 'primary' && 'bg-emerald-500 text-white',
|
|
167
|
+
disabled && 'opacity-50 cursor-not-allowed'
|
|
168
|
+
)} />
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### shadcn/ui
|
|
172
|
+
- MUST customize defaults — never ship generic appearance
|
|
173
|
+
- Override `--radius`, color palette, shadow depth
|
|
174
|
+
- All components should match project's aesthetic direction
|
|
175
|
+
|
|
176
|
+
### CSS Variables for Design Tokens
|
|
177
|
+
```css
|
|
178
|
+
:root {
|
|
179
|
+
--color-primary: oklch(0.7 0.15 160);
|
|
180
|
+
--color-surface: oklch(0.98 0 0);
|
|
181
|
+
--radius-default: 0.75rem;
|
|
182
|
+
--shadow-card: 0 20px 40px -15px rgba(0,0,0,0.05);
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## Dependency Verification [MANDATORY]
|
|
189
|
+
|
|
190
|
+
Before importing ANY 3rd party library:
|
|
191
|
+
1. Check `package.json`
|
|
192
|
+
2. If missing, output `npm install <package>` BEFORE providing code
|
|
193
|
+
3. **Never assume** a library exists
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Icons
|
|
198
|
+
|
|
199
|
+
Use EXACTLY `@phosphor-icons/react` or `@radix-ui/react-icons` as import paths.
|
|
200
|
+
Standardize `strokeWidth` globally (e.g., exclusively `1.5` or `2.0`).
|
|
201
|
+
**NEVER** use emoji — see `anti-slop.md`.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Performance
|
|
206
|
+
|
|
207
|
+
### Bundle Optimization
|
|
208
|
+
| Heavy Package | Size | Alternative |
|
|
209
|
+
| ------------- | ----- | ------------------------------ |
|
|
210
|
+
| moment | 290KB | date-fns (12KB) or dayjs (2KB) |
|
|
211
|
+
| lodash | 71KB | lodash-es with tree-shaking |
|
|
212
|
+
| axios | 14KB | Native fetch or ky (3KB) |
|
|
213
|
+
| @mui/material | Large | shadcn/ui or Radix UI |
|
|
214
|
+
|
|
215
|
+
### Rendering
|
|
216
|
+
- Parallel fetch: `Promise.all([getUser(), getStats()])`
|
|
217
|
+
- Streaming: `<Suspense fallback={<Skeleton />}>`
|
|
218
|
+
- Avoid waterfall: never fetch sequentially in nested components
|
|
219
|
+
- `React.memo` for expensive pure components
|
|
220
|
+
- `useMemo` / `useCallback` only when measured, not preemptively
|
|
221
|
+
|
|
222
|
+
### Image Optimization
|
|
223
|
+
```tsx
|
|
224
|
+
// Above fold — load immediately
|
|
225
|
+
<Image src="/hero.jpg" alt="Hero" width={1200} height={600} priority />
|
|
226
|
+
|
|
227
|
+
// Responsive fill
|
|
228
|
+
<div className="relative aspect-video">
|
|
229
|
+
<Image src="/product.jpg" alt="Product" fill
|
|
230
|
+
sizes="(max-width: 768px) 100vw, 50vw"
|
|
231
|
+
className="object-cover" />
|
|
232
|
+
</div>
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
---
|
|
236
|
+
|
|
237
|
+
## Testing
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
import { render, screen } from '@testing-library/react';
|
|
241
|
+
import userEvent from '@testing-library/user-event';
|
|
242
|
+
|
|
243
|
+
test('button triggers action', async () => {
|
|
244
|
+
const onClick = vi.fn();
|
|
245
|
+
render(<Button onClick={onClick}>Click</Button>);
|
|
246
|
+
await userEvent.click(screen.getByRole('button'));
|
|
247
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('dialog is accessible', () => {
|
|
251
|
+
render(<Dialog open title="Confirm" />);
|
|
252
|
+
expect(screen.getByRole('dialog')).toHaveAttribute('aria-labelledby');
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Responsiveness
|
|
259
|
+
|
|
260
|
+
- Standardize breakpoints: `sm`, `md`, `lg`, `xl`
|
|
261
|
+
- Contain layouts: `max-w-[1400px] mx-auto` or `max-w-7xl`
|
|
262
|
+
- **NEVER** `h-screen` for full-height. ALWAYS `min-h-[100dvh]`
|
|
263
|
+
- **NEVER** `w-[calc(33%-1rem)]`. ALWAYS CSS Grid
|
|
264
|
+
- DESIGN_VARIANCE levels 4-10: aggressive single-column fallback on `< 768px`
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# Svelte 5 / SvelteKit 2 Stack
|
|
2
|
+
|
|
3
|
+
Last reviewed: 2026-06-16
|
|
4
|
+
Applies to: Svelte 5.x (Runes), SvelteKit 2.x
|
|
5
|
+
When to read: Building or modifying Svelte/SvelteKit projects
|
|
6
|
+
Canonical owner: dev-frontend
|
|
7
|
+
|
|
8
|
+
Read `core/aesthetics.md` + `core/anti-slop.md` first.
|
|
9
|
+
|
|
10
|
+
## 1. Runes (Svelte 5)
|
|
11
|
+
|
|
12
|
+
Svelte 5 replaces stores and reactive declarations with **Runes** — explicit reactivity primitives:
|
|
13
|
+
|
|
14
|
+
| Rune | Replaces | Purpose |
|
|
15
|
+
|------|----------|---------|
|
|
16
|
+
| `$state()` | `let x = 0` (reactive) | Declare reactive state |
|
|
17
|
+
| `$derived()` | `$: doubled = x * 2` | Computed value from state |
|
|
18
|
+
| `$effect()` | `$: { sideEffect() }` | Side effect on state change |
|
|
19
|
+
| `$props()` | `export let prop` | Declare component props |
|
|
20
|
+
| `$bindable()` | `export let value` (two-way) | Two-way bindable prop |
|
|
21
|
+
| `$inspect()` | `console.log` debugging | Dev-only reactive logging (stripped in prod) |
|
|
22
|
+
|
|
23
|
+
```svelte
|
|
24
|
+
<script>
|
|
25
|
+
let { title, onSave } = $props();
|
|
26
|
+
let count = $state(0);
|
|
27
|
+
let doubled = $derived(count * 2);
|
|
28
|
+
|
|
29
|
+
$effect(() => {
|
|
30
|
+
console.log(`count changed to ${count}`);
|
|
31
|
+
return () => { /* cleanup */ };
|
|
32
|
+
});
|
|
33
|
+
</script>
|
|
34
|
+
|
|
35
|
+
<button onclick={() => count++}>
|
|
36
|
+
{title}: {count} (doubled: {doubled})
|
|
37
|
+
</button>
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Rules:
|
|
41
|
+
- Use `$state()` for all mutable reactive values — bare `let` is no longer reactive in Svelte 5.
|
|
42
|
+
- Prefer `$derived()` over `$effect()` for computed values — effects are for side effects only.
|
|
43
|
+
- `$effect()` runs after DOM update. Use `$effect.pre()` only when you need pre-DOM-update timing.
|
|
44
|
+
- Use `$inspect()` for debugging, never `console.log` inside `$effect()` — `$inspect` is stripped from production builds.
|
|
45
|
+
- Destructure `$props()` at the top of `<script>` — do not access `$$props` or `$$restProps` (Svelte 4 patterns).
|
|
46
|
+
|
|
47
|
+
## 2. SvelteKit 2
|
|
48
|
+
|
|
49
|
+
### Routing & Data Loading
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
src/routes/
|
|
53
|
+
├── +layout.svelte # root layout
|
|
54
|
+
├── +layout.server.ts # root layout data (runs on server)
|
|
55
|
+
├── +page.svelte # home page
|
|
56
|
+
├── +page.server.ts # home page data (runs on server)
|
|
57
|
+
├── blog/
|
|
58
|
+
│ ├── +page.svelte # blog list
|
|
59
|
+
│ ├── +page.server.ts # blog list data
|
|
60
|
+
│ └── [slug]/
|
|
61
|
+
│ ├── +page.svelte # blog post
|
|
62
|
+
│ └── +page.server.ts # blog post data
|
|
63
|
+
└── api/
|
|
64
|
+
└── posts/
|
|
65
|
+
└── +server.ts # API endpoint
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| File | Runs On | Purpose |
|
|
69
|
+
|------|---------|---------|
|
|
70
|
+
| `+page.server.ts` → `load()` | Server only | Fetch data, access DB, read secrets |
|
|
71
|
+
| `+page.ts` → `load()` | Server + client | Universal data loading (no secrets) |
|
|
72
|
+
| `+page.svelte` | Client (hydrated) | Render UI with loaded data |
|
|
73
|
+
| `+server.ts` | Server only | API endpoints (GET, POST, PUT, DELETE) |
|
|
74
|
+
| `+layout.server.ts` | Server only | Shared layout data (auth, user session) |
|
|
75
|
+
|
|
76
|
+
### Form Actions
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// +page.server.ts
|
|
80
|
+
export const actions = {
|
|
81
|
+
create: async ({ request, locals }) => {
|
|
82
|
+
const data = await request.formData();
|
|
83
|
+
const title = data.get('title');
|
|
84
|
+
|
|
85
|
+
if (!title) {
|
|
86
|
+
return fail(400, { title, missing: true });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await db.posts.create({ data: { title, author: locals.user.id } });
|
|
90
|
+
redirect(303, '/blog');
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```svelte
|
|
96
|
+
<!-- +page.svelte -->
|
|
97
|
+
<script>
|
|
98
|
+
import { enhance } from '$app/forms';
|
|
99
|
+
let { form } = $props();
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<form method="POST" action="?/create" use:enhance>
|
|
103
|
+
<input name="title" value={form?.title ?? ''} />
|
|
104
|
+
{#if form?.missing}<p class="error">Title required</p>{/if}
|
|
105
|
+
<button>Create</button>
|
|
106
|
+
</form>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Rules:
|
|
110
|
+
- Always use `+page.server.ts` for data that requires secrets, DB access, or auth checks.
|
|
111
|
+
- Use form actions for mutations — progressive enhancement works without JavaScript.
|
|
112
|
+
- Validate server-side in form actions — client validation is UX, not security.
|
|
113
|
+
- Use `redirect()` and `error()` from `@sveltejs/kit`, not manual response construction.
|
|
114
|
+
- Use `$app/stores` sparingly in Svelte 5 — prefer `$props()` data flow from `load()`.
|
|
115
|
+
|
|
116
|
+
### SvelteKit Adapters
|
|
117
|
+
|
|
118
|
+
| Adapter | Target | Install |
|
|
119
|
+
|---------|--------|---------|
|
|
120
|
+
| `@sveltejs/adapter-auto` | Auto-detect (Vercel, Netlify, Cloudflare) | Default — included in new projects |
|
|
121
|
+
| `@sveltejs/adapter-node` | Node.js server (Docker, VPS) | `npm i -D @sveltejs/adapter-node` |
|
|
122
|
+
| `@sveltejs/adapter-static` | Static site (GitHub Pages, S3) | `npm i -D @sveltejs/adapter-static` |
|
|
123
|
+
| `@sveltejs/adapter-vercel` | Vercel (edge/serverless) | `npm i -D @sveltejs/adapter-vercel` |
|
|
124
|
+
| `@sveltejs/adapter-cloudflare` | Cloudflare Workers/Pages | `npm i -D @sveltejs/adapter-cloudflare` |
|
|
125
|
+
|
|
126
|
+
### Performance Characteristics
|
|
127
|
+
|
|
128
|
+
| Metric | Svelte 5 | React 19 | Vue 3.5 |
|
|
129
|
+
|--------|----------|----------|---------|
|
|
130
|
+
| Bundle size (hello world) | ~2 KB | ~45 KB | ~30 KB |
|
|
131
|
+
| Reactivity model | Compile-time (runes) | Runtime (fiber + hooks) | Runtime (proxy) |
|
|
132
|
+
| Virtual DOM | No — direct DOM updates | Yes | Yes |
|
|
133
|
+
| SSR streaming | SvelteKit `+page.server.ts` | React Server Components | Nuxt `useAsyncData` |
|
|
134
|
+
| Built-in transitions | Yes (`svelte/transition`) | No (need library) | Yes (`<Transition>`) |
|
|
135
|
+
| Scoped CSS | Default (`<style>`) | No (need CSS modules/Tailwind) | Default (`<style scoped>`) |
|
|
136
|
+
|
|
137
|
+
## 3. Component Patterns
|
|
138
|
+
|
|
139
|
+
### Snippets (Svelte 5 — replaces slots)
|
|
140
|
+
|
|
141
|
+
```svelte
|
|
142
|
+
<!-- Parent -->
|
|
143
|
+
<Card>
|
|
144
|
+
{#snippet header()}
|
|
145
|
+
<h2>Title</h2>
|
|
146
|
+
{/snippet}
|
|
147
|
+
{#snippet footer()}
|
|
148
|
+
<button>Action</button>
|
|
149
|
+
{/snippet}
|
|
150
|
+
<p>Default content</p>
|
|
151
|
+
</Card>
|
|
152
|
+
|
|
153
|
+
<!-- Card.svelte -->
|
|
154
|
+
<script>
|
|
155
|
+
let { header, footer, children } = $props();
|
|
156
|
+
</script>
|
|
157
|
+
|
|
158
|
+
<div class="card">
|
|
159
|
+
{#if header}{@render header()}{/if}
|
|
160
|
+
{@render children()}
|
|
161
|
+
{#if footer}{@render footer()}{/if}
|
|
162
|
+
</div>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Transitions & Animations
|
|
166
|
+
|
|
167
|
+
```svelte
|
|
168
|
+
<script>
|
|
169
|
+
import { fly, fade } from 'svelte/transition';
|
|
170
|
+
import { flip } from 'svelte/animate';
|
|
171
|
+
|
|
172
|
+
let items = $state([]);
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
{#each items as item (item.id)}
|
|
176
|
+
<div
|
|
177
|
+
animate:flip={{ duration: 300 }}
|
|
178
|
+
in:fly={{ y: 20, duration: 200 }}
|
|
179
|
+
out:fade={{ duration: 150 }}
|
|
180
|
+
>
|
|
181
|
+
{item.name}
|
|
182
|
+
</div>
|
|
183
|
+
{/each}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Rules:
|
|
187
|
+
- Use Snippets instead of slots — slots are deprecated in Svelte 5.
|
|
188
|
+
- Use `{@render}` for snippet rendering, not `<svelte:component>`.
|
|
189
|
+
- Scoped `<style>` is default — use `:global()` only when intentionally escaping scope.
|
|
190
|
+
- Prefer CSS transitions for simple effects; use Svelte transitions for enter/exit/list animations.
|
|
191
|
+
- Always provide `(key)` in `{#each}` blocks for stable identity.
|
|
192
|
+
|
|
193
|
+
## 4. Migration from Svelte 4
|
|
194
|
+
|
|
195
|
+
| Svelte 4 | Svelte 5 | Migration |
|
|
196
|
+
|----------|----------|-----------|
|
|
197
|
+
| `export let prop` | `let { prop } = $props()` | Destructure from `$props()` |
|
|
198
|
+
| `$: derived = x * 2` | `let derived = $derived(x * 2)` | Wrap in `$derived()` |
|
|
199
|
+
| `$: { sideEffect() }` | `$effect(() => { sideEffect() })` | Wrap in `$effect()` |
|
|
200
|
+
| `<slot />` | `{@render children()}` | Replace slots with snippets |
|
|
201
|
+
| `<slot name="x" />` | `{@render x()}` | Named slots → named snippets |
|
|
202
|
+
| `$$props` / `$$restProps` | `let { ...rest } = $props()` | Destructure with rest |
|
|
203
|
+
| Writable stores | `$state()` in `.svelte.ts` | Move to Rune-based state |
|
|
204
|
+
| `createEventDispatcher()` | Callback props | Pass functions via `$props()` |
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
# Automated migration (partial — review output manually)
|
|
208
|
+
npx svelte-migrate svelte-5
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Rules:
|
|
212
|
+
- Migrate one component at a time — Svelte 5 components can coexist with Svelte 4 in the same project.
|
|
213
|
+
- Test each migrated component individually before proceeding.
|
|
214
|
+
- Stores in `.js`/`.ts` files still work — migrate to `.svelte.ts` Rune-based state when touching the file.
|
|
215
|
+
- `createEventDispatcher` → callback props is the biggest behavioral change; verify all event consumers.
|
|
216
|
+
|
|
217
|
+
## 5. Anti-Patterns
|
|
218
|
+
|
|
219
|
+
| Banned | Symptom | Fix |
|
|
220
|
+
|--------|---------|-----|
|
|
221
|
+
| `$effect()` for derived values | Unnecessary re-runs, stale value bugs | Use `$derived()` instead |
|
|
222
|
+
| Missing `(key)` in `{#each}` | Animation glitches, wrong element updates | Always provide a stable key |
|
|
223
|
+
| `$$props` in Svelte 5 | Deprecated, loses type safety | Destructure from `$props()` |
|
|
224
|
+
| Bare `let` expecting reactivity | Value does not update in template | Use `$state()` for reactive values |
|
|
225
|
+
| `on:click` syntax in Svelte 5 | Deprecated event directive | Use `onclick` attribute |
|
|
226
|
+
|
|
227
|
+
## Pre-flight
|
|
228
|
+
|
|
229
|
+
- [ ] All reactive state uses `$state()`, not bare `let`
|
|
230
|
+
- [ ] Computed values use `$derived()`, not `$effect()`
|
|
231
|
+
- [ ] Components use `$props()` destructuring, not `export let`
|
|
232
|
+
- [ ] `{#each}` blocks have stable keys
|
|
233
|
+
- [ ] Server-only data in `+page.server.ts`, not `+page.ts`
|
|
234
|
+
- [ ] Form actions validate server-side
|
|
235
|
+
- [ ] Slots migrated to Snippets (`{@render}`) in new/updated components
|