codeforge-dev 1.4.0
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/.devcontainer/.env +22 -0
- package/.devcontainer/CHANGELOG.md +197 -0
- package/.devcontainer/CLAUDE.md +117 -0
- package/.devcontainer/README.md +222 -0
- package/.devcontainer/config/main-system-prompt.md +502 -0
- package/.devcontainer/config/settings.json +47 -0
- package/.devcontainer/devcontainer.json +94 -0
- package/.devcontainer/features/README.md +113 -0
- package/.devcontainer/features/agent-browser/README.md +65 -0
- package/.devcontainer/features/agent-browser/devcontainer-feature.json +23 -0
- package/.devcontainer/features/agent-browser/install.sh +79 -0
- package/.devcontainer/features/ast-grep/README.md +24 -0
- package/.devcontainer/features/ast-grep/devcontainer-feature.json +24 -0
- package/.devcontainer/features/ast-grep/install.sh +51 -0
- package/.devcontainer/features/ccstatusline/README.md +296 -0
- package/.devcontainer/features/ccstatusline/devcontainer-feature.json +19 -0
- package/.devcontainer/features/ccstatusline/install.sh +290 -0
- package/.devcontainer/features/ccusage/README.md +205 -0
- package/.devcontainer/features/ccusage/devcontainer-feature.json +38 -0
- package/.devcontainer/features/ccusage/install.sh +132 -0
- package/.devcontainer/features/claude-code/README.md +498 -0
- package/.devcontainer/features/claude-code/config/settings.json +36 -0
- package/.devcontainer/features/claude-code/config/system-prompt.md +118 -0
- package/.devcontainer/features/claude-code/config/world-building-sp.md +1432 -0
- package/.devcontainer/features/claude-code/devcontainer-feature.json +42 -0
- package/.devcontainer/features/claude-code/install.sh +466 -0
- package/.devcontainer/features/claude-monitor/README.md +74 -0
- package/.devcontainer/features/claude-monitor/devcontainer-feature.json +38 -0
- package/.devcontainer/features/claude-monitor/install.sh +99 -0
- package/.devcontainer/features/lsp-servers/README.md +85 -0
- package/.devcontainer/features/lsp-servers/devcontainer-feature.json +40 -0
- package/.devcontainer/features/lsp-servers/install.sh +116 -0
- package/.devcontainer/features/mcp-qdrant/CHANGES.md +399 -0
- package/.devcontainer/features/mcp-qdrant/README.md +474 -0
- package/.devcontainer/features/mcp-qdrant/devcontainer-feature.json +57 -0
- package/.devcontainer/features/mcp-qdrant/install.sh +295 -0
- package/.devcontainer/features/mcp-qdrant/poststart-hook.sh +129 -0
- package/.devcontainer/features/mcp-reasoner/README.md +177 -0
- package/.devcontainer/features/mcp-reasoner/devcontainer-feature.json +20 -0
- package/.devcontainer/features/mcp-reasoner/install.sh +177 -0
- package/.devcontainer/features/mcp-reasoner/poststart-hook.sh +67 -0
- package/.devcontainer/features/notify-hook/README.md +86 -0
- package/.devcontainer/features/notify-hook/devcontainer-feature.json +23 -0
- package/.devcontainer/features/notify-hook/install.sh +38 -0
- package/.devcontainer/features/splitrail/README.md +140 -0
- package/.devcontainer/features/splitrail/devcontainer-feature.json +34 -0
- package/.devcontainer/features/splitrail/install.sh +129 -0
- package/.devcontainer/features/tree-sitter/README.md +138 -0
- package/.devcontainer/features/tree-sitter/devcontainer-feature.json +52 -0
- package/.devcontainer/features/tree-sitter/install.sh +173 -0
- package/.devcontainer/plugins/devs-marketplace/.claude-plugin/marketplace.json +106 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-formatter/scripts/format-file.py +101 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/auto-linter/scripts/lint-file.py +137 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/SKILL.md +387 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/cli-flags-and-output.md +312 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/claude-code-headless/references/sdk-and-mcp.md +569 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/SKILL.md +309 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/compose-services.md +438 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker/references/dockerfile-patterns.md +340 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/SKILL.md +412 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/container-lifecycle.md +388 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/docker-py/references/resources-and-security.md +444 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/SKILL.md +344 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/middleware-and-lifespan.md +254 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/pydantic-models.md +245 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/routing-and-dependencies.md +255 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/fastapi/references/sse-and-streaming.md +318 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/SKILL.md +345 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/agents-and-tools.md +271 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/pydantic-ai/references/models-and-streaming.md +422 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/SKILL.md +220 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/cross-vendor-principles.md +139 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/patterns-and-antipatterns.md +376 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/skill-building/references/skill-authoring-patterns.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/SKILL.md +329 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/advanced-queries.md +314 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/javascript-patterns.md +323 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/python-patterns.md +354 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/sqlite/references/schema-and-pragmas.md +326 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/SKILL.md +356 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/ai-sdk-svelte.md +128 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/component-patterns.md +332 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/layercake.md +203 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/migration-guide.md +350 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/runes-and-reactivity.md +328 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/spa-and-routing.md +262 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/svelte5/references/svelte-dnd-action.md +181 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/SKILL.md +414 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/fastapi-testing.md +411 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codedirective-skills/skills/testing/references/svelte-testing.md +538 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/codeforge-lsp/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/dangerous-command-blocker/scripts/block-dangerous.py +110 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/notify-hook/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/planning-reminder/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/.claude-plugin/plugin.json +7 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/hooks/hooks.json +17 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/protected-files-guard/scripts/guard-protected.py +108 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272create-pr.md +337 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272new.md +166 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272review-commit.md +290 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/commands/ticket/357/200/272work.md +257 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/plugin.json +8 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/ticket-workflow/.claude-plugin/system-prompt.md +184 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/.claude-plugin/plugin.json +6 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/config/planning-instructions.md +14 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/functional-conjuring-map.md +989 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/hooks/hooks.json +33 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/__pycache__/post-enhance-task.cpython-314.pyc +0 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhance-planning.py +71 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-plan.sh +68 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/enhancers/enhance-task.sh +120 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-plan.py +133 -0
- package/.devcontainer/plugins/devs-marketplace/plugins/workflow-enhancer/scripts/post-enhance-task.py +253 -0
- package/.devcontainer/scripts/setup-aliases.sh +80 -0
- package/.devcontainer/scripts/setup-config.sh +28 -0
- package/.devcontainer/scripts/setup-irie-claude.sh +32 -0
- package/.devcontainer/scripts/setup-plugins.sh +80 -0
- package/.devcontainer/scripts/setup.sh +58 -0
- package/LICENSE.txt +674 -0
- package/README.md +267 -0
- package/package.json +44 -0
- package/setup.js +83 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
# Svelte 4 → Svelte 5 Migration Guide
|
|
2
|
+
|
|
3
|
+
Ten migration patterns with complete before/after code. Each pattern includes rationale and edge cases to handle during conversion.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Reactive State Declaration
|
|
8
|
+
|
|
9
|
+
### Svelte 4
|
|
10
|
+
```svelte
|
|
11
|
+
<script>
|
|
12
|
+
let count = 0;
|
|
13
|
+
let user = { name: 'Alice', age: 30 };
|
|
14
|
+
</script>
|
|
15
|
+
<button on:click={() => count++}>{count}</button>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Svelte 5
|
|
19
|
+
```svelte
|
|
20
|
+
<script>
|
|
21
|
+
let count = $state(0);
|
|
22
|
+
let user = $state({ name: 'Alice', age: 30 });
|
|
23
|
+
</script>
|
|
24
|
+
<button onclick={() => count++}>{count}</button>
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Rationale
|
|
28
|
+
Svelte 4 relied on compile-time analysis of top-level `let` declarations to infer reactivity. This was implicit and fragile — reassigning inside callbacks, passing to functions, or destructuring could silently break reactivity. `$state` makes the reactive intent explicit.
|
|
29
|
+
|
|
30
|
+
### Edge Cases
|
|
31
|
+
- Variables that are assigned once and never change do not need `$state`. Plain `const` or `let` works for non-reactive values.
|
|
32
|
+
- Objects and arrays become deeply reactive proxies. If a variable holds large, frequently replaced data (API responses), consider `$state.raw()` instead.
|
|
33
|
+
- Module-level variables in `.js` files are not reactive. Rename the file to `.svelte.js` to enable runes.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 2. Reactive Derivations
|
|
38
|
+
|
|
39
|
+
### Svelte 4
|
|
40
|
+
```svelte
|
|
41
|
+
<script>
|
|
42
|
+
let items = [];
|
|
43
|
+
$: remaining = items.filter(i => !i.done).length;
|
|
44
|
+
$: summary = `${remaining} of ${items.length} left`;
|
|
45
|
+
</script>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Svelte 5
|
|
49
|
+
```svelte
|
|
50
|
+
<script>
|
|
51
|
+
let items = $state([]);
|
|
52
|
+
let remaining = $derived(items.filter(i => !i.done).length);
|
|
53
|
+
let summary = $derived(`${remaining} of ${items.length} left`);
|
|
54
|
+
</script>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Rationale
|
|
58
|
+
`$:` reactive declarations had confusing semantics — they could be statements or expressions, ran in topological order that was hard to predict, and failed silently when dependencies were indirect. `$derived` is always an expression with synchronous consistency.
|
|
59
|
+
|
|
60
|
+
### Edge Cases
|
|
61
|
+
- Multi-statement computations need `$derived.by(() => { ... })`.
|
|
62
|
+
- `$derived` cannot have side effects. Move side effects to `$effect`.
|
|
63
|
+
- Circular derivations are compile-time errors in Svelte 5 (previously they caused infinite loops silently).
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 3. Reactive Side Effects
|
|
68
|
+
|
|
69
|
+
### Svelte 4
|
|
70
|
+
```svelte
|
|
71
|
+
<script>
|
|
72
|
+
let query = '';
|
|
73
|
+
$: {
|
|
74
|
+
console.log('Query changed:', query);
|
|
75
|
+
document.title = `Search: ${query}`;
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Svelte 5
|
|
81
|
+
```svelte
|
|
82
|
+
<script>
|
|
83
|
+
let query = $state('');
|
|
84
|
+
|
|
85
|
+
$effect(() => {
|
|
86
|
+
console.log('Query changed:', query);
|
|
87
|
+
document.title = `Search: ${query}`;
|
|
88
|
+
});
|
|
89
|
+
</script>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Rationale
|
|
93
|
+
`$:` blocks for side effects looked identical to derivations, making intent unclear. `$effect` clearly signals "this code runs for side effects, not to produce a value." It also provides a cleanup function mechanism that `$:` lacked.
|
|
94
|
+
|
|
95
|
+
### Edge Cases
|
|
96
|
+
- `$effect` runs asynchronously after DOM updates. Code that previously ran synchronously in `$:` may behave differently.
|
|
97
|
+
- `$effect` tracks dependencies automatically — variables read during execution are tracked. Variables read only inside callbacks (e.g., `setTimeout`) are not tracked.
|
|
98
|
+
- Return a cleanup function for teardown: `$effect(() => { ... return () => cleanup(); });`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 4. Component Props
|
|
103
|
+
|
|
104
|
+
### Svelte 4
|
|
105
|
+
```svelte
|
|
106
|
+
<script>
|
|
107
|
+
export let title;
|
|
108
|
+
export let variant = 'primary';
|
|
109
|
+
export let items = [];
|
|
110
|
+
</script>
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Svelte 5
|
|
114
|
+
```svelte
|
|
115
|
+
<script>
|
|
116
|
+
let { title, variant = 'primary', items = [] } = $props();
|
|
117
|
+
</script>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Rationale
|
|
121
|
+
`export let` overloaded the `export` keyword — props were not actual exports. `$props()` uses standard JavaScript destructuring, making defaults and rest patterns natural.
|
|
122
|
+
|
|
123
|
+
### Edge Cases
|
|
124
|
+
- `$$props` and `$$restProps` are replaced by rest syntax: `let { known, ...rest } = $props();`
|
|
125
|
+
- TypeScript: define an interface and apply it to the destructuring — `let { title }: Props = $props();`
|
|
126
|
+
- Props are read-only by default. Attempting to reassign a prop causes a warning. Use `$bindable()` for two-way binding.
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 5. DOM Event Handlers
|
|
131
|
+
|
|
132
|
+
### Svelte 4
|
|
133
|
+
```svelte
|
|
134
|
+
<button on:click={handleClick}>Click</button>
|
|
135
|
+
<button on:click|preventDefault={handleSubmit}>Submit</button>
|
|
136
|
+
<input on:input={(e) => query = e.target.value} />
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Svelte 5
|
|
140
|
+
```svelte
|
|
141
|
+
<button onclick={handleClick}>Click</button>
|
|
142
|
+
<button onclick={(e) => { e.preventDefault(); handleSubmit(e); }}>Submit</button>
|
|
143
|
+
<input oninput={(e) => query = e.currentTarget.value} />
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Rationale
|
|
147
|
+
Svelte 5 uses standard HTML event attributes, aligning with the DOM API and removing the need for a custom directive syntax. This improves interoperability with tools and editors that understand standard HTML.
|
|
148
|
+
|
|
149
|
+
### Edge Cases
|
|
150
|
+
- Event modifiers (`|preventDefault`, `|stopPropagation`) are removed. Call the methods directly in the handler.
|
|
151
|
+
- `e.target` is replaced by `e.currentTarget` for TypeScript correctness (the element the handler is attached to, not the event origin).
|
|
152
|
+
- Multiple handlers on the same event: use a single handler that calls multiple functions, or compose with a helper.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
## 6. Callback Props (Replacing createEventDispatcher)
|
|
157
|
+
|
|
158
|
+
### Svelte 4
|
|
159
|
+
```svelte
|
|
160
|
+
<!-- Child.svelte -->
|
|
161
|
+
<script>
|
|
162
|
+
import { createEventDispatcher } from 'svelte';
|
|
163
|
+
const dispatch = createEventDispatcher();
|
|
164
|
+
</script>
|
|
165
|
+
<button on:click={() => dispatch('select', { id: 1 })}>Select</button>
|
|
166
|
+
|
|
167
|
+
<!-- Parent.svelte -->
|
|
168
|
+
<Child on:select={(e) => handleSelect(e.detail)} />
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Svelte 5
|
|
172
|
+
```svelte
|
|
173
|
+
<!-- Child.svelte -->
|
|
174
|
+
<script>
|
|
175
|
+
let { onselect } = $props();
|
|
176
|
+
</script>
|
|
177
|
+
<button onclick={() => onselect?.({ id: 1 })}>Select</button>
|
|
178
|
+
|
|
179
|
+
<!-- Parent.svelte -->
|
|
180
|
+
<Child onselect={(data) => handleSelect(data)} />
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Rationale
|
|
184
|
+
`createEventDispatcher` used a custom event system with `.detail` unwrapping. Callback props are plain functions — typed, composable, and requiring no special API knowledge.
|
|
185
|
+
|
|
186
|
+
### Edge Cases
|
|
187
|
+
- Convention: prefix with `on` (e.g., `onselect`, `onchange`, `ondelete`). This matches DOM event naming.
|
|
188
|
+
- Optional callbacks: use optional chaining (`onselect?.()`) to safely skip when the parent does not provide a handler.
|
|
189
|
+
- Data is passed directly as arguments, not wrapped in `event.detail`.
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 7. Snippets (Replacing Slots)
|
|
194
|
+
|
|
195
|
+
### Svelte 4
|
|
196
|
+
```svelte
|
|
197
|
+
<!-- Card.svelte -->
|
|
198
|
+
<div class="card">
|
|
199
|
+
<slot name="header" />
|
|
200
|
+
<slot />
|
|
201
|
+
<slot name="footer" />
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<!-- Parent -->
|
|
205
|
+
<Card>
|
|
206
|
+
<h2 slot="header">Title</h2>
|
|
207
|
+
<p>Content</p>
|
|
208
|
+
<small slot="footer">Footer</small>
|
|
209
|
+
</Card>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Svelte 5
|
|
213
|
+
```svelte
|
|
214
|
+
<!-- Card.svelte -->
|
|
215
|
+
<script>
|
|
216
|
+
let { header, children, footer } = $props();
|
|
217
|
+
</script>
|
|
218
|
+
<div class="card">
|
|
219
|
+
{@render header?.()}
|
|
220
|
+
{@render children?.()}
|
|
221
|
+
{@render footer?.()}
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<!-- Parent -->
|
|
225
|
+
<Card>
|
|
226
|
+
{#snippet header()}
|
|
227
|
+
<h2>Title</h2>
|
|
228
|
+
{/snippet}
|
|
229
|
+
|
|
230
|
+
<p>Content</p>
|
|
231
|
+
|
|
232
|
+
{#snippet footer()}
|
|
233
|
+
<small>Footer</small>
|
|
234
|
+
{/snippet}
|
|
235
|
+
</Card>
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Rationale
|
|
239
|
+
Slots had limited type safety and could not receive parameters without the awkward `let:` directive. Snippets are typed, parameterized, and composable — they are first-class values that can be stored, passed, and conditionally rendered.
|
|
240
|
+
|
|
241
|
+
### Edge Cases
|
|
242
|
+
- Default content: use the nullish check pattern — `{@render header?.() ?? fallbackSnippet()}` or `{#if header}{@render header()}{:else}Default{/if}`.
|
|
243
|
+
- `<slot let:item>` (slot props) → parameterized snippets: `{#snippet row(item)}...{/snippet}`.
|
|
244
|
+
- Snippet type in TypeScript: `import type { Snippet } from 'svelte';`
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 8. Bindable Props
|
|
249
|
+
|
|
250
|
+
### Svelte 4
|
|
251
|
+
```svelte
|
|
252
|
+
<!-- Input.svelte -->
|
|
253
|
+
<script>
|
|
254
|
+
export let value = '';
|
|
255
|
+
</script>
|
|
256
|
+
<input bind:value />
|
|
257
|
+
|
|
258
|
+
<!-- Parent -->
|
|
259
|
+
<Input bind:value={name} />
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
### Svelte 5
|
|
263
|
+
```svelte
|
|
264
|
+
<!-- Input.svelte -->
|
|
265
|
+
<script>
|
|
266
|
+
let { value = $bindable('') } = $props();
|
|
267
|
+
</script>
|
|
268
|
+
<input bind:value />
|
|
269
|
+
|
|
270
|
+
<!-- Parent -->
|
|
271
|
+
<Input bind:value={name} />
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Rationale
|
|
275
|
+
In Svelte 4, all `export let` props were implicitly bindable, creating an unclear API surface. `$bindable()` makes two-way binding an explicit opt-in — consumers know which props support `bind:`.
|
|
276
|
+
|
|
277
|
+
### Edge Cases
|
|
278
|
+
- The default value goes inside `$bindable()`: `$bindable('')`, not `$bindable() ?? ''`.
|
|
279
|
+
- Parent still uses `bind:value={variable}` syntax — the binding syntax is unchanged on the consumer side.
|
|
280
|
+
- Only mark props as bindable when bidirectional data flow is the intended component API.
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## 9. Dynamic Components
|
|
285
|
+
|
|
286
|
+
### Svelte 4
|
|
287
|
+
```svelte
|
|
288
|
+
<script>
|
|
289
|
+
import Home from './Home.svelte';
|
|
290
|
+
import About from './About.svelte';
|
|
291
|
+
let current = Home;
|
|
292
|
+
</script>
|
|
293
|
+
<svelte:component this={current} />
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Svelte 5
|
|
297
|
+
```svelte
|
|
298
|
+
<script>
|
|
299
|
+
import Home from './Home.svelte';
|
|
300
|
+
import About from './About.svelte';
|
|
301
|
+
let Current = $state(Home);
|
|
302
|
+
</script>
|
|
303
|
+
<Current />
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Rationale
|
|
307
|
+
`<svelte:component>` was a workaround for a compiler limitation. Svelte 5 treats components as values — any variable holding a component constructor can be rendered directly as a tag.
|
|
308
|
+
|
|
309
|
+
### Edge Cases
|
|
310
|
+
- The variable name must start with an uppercase letter to distinguish from HTML elements.
|
|
311
|
+
- `<svelte:component>` still works for backwards compatibility but is deprecated.
|
|
312
|
+
- Props can be passed normally: `<Current {title} {data} />`.
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## 10. Imperative Component API (mount / unmount)
|
|
317
|
+
|
|
318
|
+
### Svelte 4
|
|
319
|
+
```js
|
|
320
|
+
import App from './App.svelte';
|
|
321
|
+
|
|
322
|
+
const app = new App({
|
|
323
|
+
target: document.getElementById('app'),
|
|
324
|
+
props: { name: 'World' }
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
app.$destroy();
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
### Svelte 5
|
|
331
|
+
```js
|
|
332
|
+
import { mount, unmount } from 'svelte';
|
|
333
|
+
import App from './App.svelte';
|
|
334
|
+
|
|
335
|
+
const app = mount(App, {
|
|
336
|
+
target: document.getElementById('app'),
|
|
337
|
+
props: { name: 'World' }
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
unmount(app);
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### Rationale
|
|
344
|
+
The `new Component()` pattern implied class instantiation, but Svelte 5 components are not classes. `mount` and `unmount` are explicit functions that clearly communicate lifecycle intent.
|
|
345
|
+
|
|
346
|
+
### Edge Cases
|
|
347
|
+
- `mount` returns an opaque reference, not a component instance with `$set` or `$on` methods. Those methods are removed.
|
|
348
|
+
- For hydration (SSR), use `hydrate` instead of `mount`.
|
|
349
|
+
- `mount` accepts an `events` option for attaching listeners, replacing the `$on` method.
|
|
350
|
+
- In SPA mode, the entry point in `src/main.js` does not typically need manual `mount` — SvelteKit handles it. Manual mounting is for embedding Svelte components in non-SvelteKit contexts.
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
# Runes and Reactivity — Deep Dive
|
|
2
|
+
|
|
3
|
+
## 1. Reactivity Model
|
|
4
|
+
|
|
5
|
+
Svelte 5 reactivity is signal-based. Each `$state` call creates a signal — a getter/setter pair tracked by the compiler. When a signal is read inside `$derived` or `$effect`, the compiler registers a dependency. When the signal changes, dependents re-execute.
|
|
6
|
+
|
|
7
|
+
Objects and arrays wrapped in `$state` are converted to deeply reactive proxies. Property access on the proxy registers fine-grained dependencies — reading `obj.name` tracks only `name`, not the entire object.
|
|
8
|
+
|
|
9
|
+
### Proxy Tracking Semantics
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
let user = $state({ name: 'Alice', age: 30 });
|
|
13
|
+
|
|
14
|
+
// Reading user.name inside $derived tracks only the name property.
|
|
15
|
+
let greeting = $derived(`Hello, ${user.name}`);
|
|
16
|
+
|
|
17
|
+
// Mutating user.age does NOT cause greeting to recalculate.
|
|
18
|
+
user.age = 31;
|
|
19
|
+
|
|
20
|
+
// Mutating user.name DOES cause greeting to recalculate.
|
|
21
|
+
user.name = 'Bob';
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Array mutations (`push`, `splice`, `pop`, etc.) are intercepted by the proxy and trigger reactive updates for any dependent that reads the array's length or iterates over it.
|
|
25
|
+
|
|
26
|
+
### When Proxies Break
|
|
27
|
+
|
|
28
|
+
Proxies do not survive structured clone, `JSON.parse(JSON.stringify())`, or transfer to web workers. Use `$state.snapshot()` before any of these operations:
|
|
29
|
+
|
|
30
|
+
```js
|
|
31
|
+
let data = $state({ items: [1, 2, 3] });
|
|
32
|
+
const plain = $state.snapshot(data);
|
|
33
|
+
postMessage(plain); // safe — plain object, no proxy
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 2. $state Deep Dive
|
|
37
|
+
|
|
38
|
+
### Basic Usage
|
|
39
|
+
|
|
40
|
+
```js
|
|
41
|
+
let count = $state(0); // primitive
|
|
42
|
+
let user = $state({ name: 'Alice' }); // deep proxy
|
|
43
|
+
let tags = $state(['svelte', 'runes']); // deep proxy array
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### $state.raw — Opt Out of Deep Tracking
|
|
47
|
+
|
|
48
|
+
Use `$state.raw()` for large data structures that are always replaced, never mutated. Only reassignment triggers reactivity.
|
|
49
|
+
|
|
50
|
+
```js
|
|
51
|
+
let rows = $state.raw(initialData);
|
|
52
|
+
|
|
53
|
+
// Does NOT trigger updates (mutation on raw state):
|
|
54
|
+
rows.push(newRow);
|
|
55
|
+
|
|
56
|
+
// Triggers updates (reassignment):
|
|
57
|
+
rows = [...rows, newRow];
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Appropriate for: API response caches, immutable data from external sources, performance-critical large lists.
|
|
61
|
+
|
|
62
|
+
### $state.snapshot — Plain Copy
|
|
63
|
+
|
|
64
|
+
Returns a non-reactive, structurally cloned copy of the reactive value.
|
|
65
|
+
|
|
66
|
+
```js
|
|
67
|
+
let form = $state({ name: '', email: '' });
|
|
68
|
+
|
|
69
|
+
function submit() {
|
|
70
|
+
const payload = $state.snapshot(form);
|
|
71
|
+
fetch('/api/submit', { method: 'POST', body: JSON.stringify(payload) });
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Class Fields
|
|
76
|
+
|
|
77
|
+
`$state` works in class fields, enabling reactive class-based stores:
|
|
78
|
+
|
|
79
|
+
```js
|
|
80
|
+
// lib/stores/counter.svelte.js
|
|
81
|
+
export class Counter {
|
|
82
|
+
count = $state(0);
|
|
83
|
+
doubled = $derived(this.count * 2);
|
|
84
|
+
|
|
85
|
+
increment() {
|
|
86
|
+
this.count++;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
reset() {
|
|
90
|
+
this.count = 0;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
```svelte
|
|
96
|
+
<script>
|
|
97
|
+
import { Counter } from '$lib/stores/counter.svelte.js';
|
|
98
|
+
const counter = new Counter();
|
|
99
|
+
</script>
|
|
100
|
+
<button onclick={() => counter.increment()}>{counter.count} (doubled: {counter.doubled})</button>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Class-based stores are ideal when a piece of state has associated behaviors (methods) and derived values. The class encapsulates the reactive graph.
|
|
104
|
+
|
|
105
|
+
## 3. $derived Deep Dive
|
|
106
|
+
|
|
107
|
+
### Sync Guarantee
|
|
108
|
+
|
|
109
|
+
`$derived` values are always synchronous and consistent. After updating a `$state` value, any `$derived` that depends on it reflects the new value immediately — there is no "stale read" window.
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
let count = $state(0);
|
|
113
|
+
let doubled = $derived(count * 2);
|
|
114
|
+
|
|
115
|
+
count = 5;
|
|
116
|
+
console.log(doubled); // 10 — immediately consistent
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Cascading Derivations
|
|
120
|
+
|
|
121
|
+
Derived values can depend on other derived values. The compiler resolves the dependency graph at compile time:
|
|
122
|
+
|
|
123
|
+
```js
|
|
124
|
+
let price = $state(100);
|
|
125
|
+
let taxRate = $state(0.08);
|
|
126
|
+
let tax = $derived(price * taxRate);
|
|
127
|
+
let total = $derived(price + tax);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### $derived.by — Multi-Statement Derivation
|
|
131
|
+
|
|
132
|
+
When derivation requires intermediate steps, use `$derived.by()` with a function body:
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
let items = $state([
|
|
136
|
+
{ name: 'A', price: 10, qty: 2 },
|
|
137
|
+
{ name: 'B', price: 5, qty: 3 }
|
|
138
|
+
]);
|
|
139
|
+
|
|
140
|
+
let orderSummary = $derived.by(() => {
|
|
141
|
+
const subtotal = items.reduce((sum, i) => sum + i.price * i.qty, 0);
|
|
142
|
+
const tax = subtotal * 0.08;
|
|
143
|
+
return { subtotal, tax, total: subtotal + tax };
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Avoid `$effect` for computations that produce values. `$derived.by()` handles complex computations while maintaining the synchronous consistency guarantee.
|
|
148
|
+
|
|
149
|
+
## 4. $effect Deep Dive
|
|
150
|
+
|
|
151
|
+
### Execution Timing
|
|
152
|
+
|
|
153
|
+
Effects run **after** the DOM has been updated. This means the DOM reflects the latest state when the effect body executes.
|
|
154
|
+
|
|
155
|
+
```js
|
|
156
|
+
let count = $state(0);
|
|
157
|
+
|
|
158
|
+
$effect(() => {
|
|
159
|
+
// DOM is already updated when this runs
|
|
160
|
+
document.title = `Count: ${count}`;
|
|
161
|
+
});
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Cleanup Functions
|
|
165
|
+
|
|
166
|
+
Return a function from `$effect` for teardown. Cleanup runs before the next effect execution and when the component is destroyed:
|
|
167
|
+
|
|
168
|
+
```js
|
|
169
|
+
$effect(() => {
|
|
170
|
+
const interval = setInterval(() => tick(), 1000);
|
|
171
|
+
return () => clearInterval(interval);
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Async Pattern
|
|
176
|
+
|
|
177
|
+
Effects cannot be `async`. Start async work inside the effect and use an AbortController for cleanup:
|
|
178
|
+
|
|
179
|
+
```js
|
|
180
|
+
$effect(() => {
|
|
181
|
+
const controller = new AbortController();
|
|
182
|
+
|
|
183
|
+
async function fetchData() {
|
|
184
|
+
try {
|
|
185
|
+
const res = await fetch(`/api/data?q=${query}`, { signal: controller.signal });
|
|
186
|
+
if (!controller.signal.aborted) {
|
|
187
|
+
results = await res.json();
|
|
188
|
+
}
|
|
189
|
+
} catch (e) {
|
|
190
|
+
if (e.name !== 'AbortError') throw e;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
fetchData();
|
|
195
|
+
return () => controller.abort();
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### $effect.pre — Before DOM Updates
|
|
200
|
+
|
|
201
|
+
`$effect.pre()` runs before the DOM is updated. Use for measuring DOM state that will change:
|
|
202
|
+
|
|
203
|
+
```js
|
|
204
|
+
$effect.pre(() => {
|
|
205
|
+
// Read scroll position before DOM update
|
|
206
|
+
previousScrollHeight = container.scrollHeight;
|
|
207
|
+
});
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
This is rarely needed. Default to `$effect()` unless DOM measurement before an update is required.
|
|
211
|
+
|
|
212
|
+
### $effect.root — Detached Effect Scope
|
|
213
|
+
|
|
214
|
+
`$effect.root()` creates an effect scope independent of any component. It returns a cleanup function that must be called manually:
|
|
215
|
+
|
|
216
|
+
```js
|
|
217
|
+
const cleanup = $effect.root(() => {
|
|
218
|
+
$effect(() => {
|
|
219
|
+
console.log('This runs outside any component');
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Later, when done:
|
|
224
|
+
cleanup();
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Use for effects in tests, in `.svelte.js` module-level logic, or when manually managing effect lifecycles.
|
|
228
|
+
|
|
229
|
+
## 5. Tracking Pitfalls
|
|
230
|
+
|
|
231
|
+
### Destructuring Loses Reactivity
|
|
232
|
+
|
|
233
|
+
Destructuring a `$state` object captures the current values as plain variables. Those variables are not tracked:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
let user = $state({ name: 'Alice', age: 30 });
|
|
237
|
+
|
|
238
|
+
// BAD: name and age are plain strings/numbers, not reactive
|
|
239
|
+
const { name, age } = user;
|
|
240
|
+
|
|
241
|
+
// GOOD: access properties on the proxy
|
|
242
|
+
$effect(() => {
|
|
243
|
+
console.log(user.name); // tracked
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Closure Reads
|
|
248
|
+
|
|
249
|
+
A value read inside a closure is tracked only if the closure runs during effect or derived execution:
|
|
250
|
+
|
|
251
|
+
```js
|
|
252
|
+
let count = $state(0);
|
|
253
|
+
|
|
254
|
+
// Tracked: the function body runs during $derived evaluation
|
|
255
|
+
let doubled = $derived(count * 2);
|
|
256
|
+
|
|
257
|
+
// NOT tracked: setTimeout callback runs outside the reactive context
|
|
258
|
+
$effect(() => {
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
console.log(count); // reads the value, but this read is NOT tracked
|
|
261
|
+
}, 1000);
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Passing State to Non-Svelte Code
|
|
266
|
+
|
|
267
|
+
External libraries, Web APIs, and non-`.svelte.js` modules do not understand Svelte proxies. Always pass `$state.snapshot()` or plain values:
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
let formData = $state({ name: '', email: '' });
|
|
271
|
+
|
|
272
|
+
// External validation library
|
|
273
|
+
import { validate } from 'some-lib';
|
|
274
|
+
const errors = validate($state.snapshot(formData));
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## 6. EventSource and SSE Streams
|
|
278
|
+
|
|
279
|
+
### Construction and Cleanup in $effect
|
|
280
|
+
|
|
281
|
+
EventSource connections follow the same `$effect` cleanup pattern as fetch and WebSocket. Construct the EventSource in the effect body and close it in the cleanup function:
|
|
282
|
+
|
|
283
|
+
```js
|
|
284
|
+
let messages = $state('');
|
|
285
|
+
let status = $state('connecting');
|
|
286
|
+
|
|
287
|
+
$effect(() => {
|
|
288
|
+
const es = new EventSource(`/api/stream?topic=${topic}`);
|
|
289
|
+
|
|
290
|
+
es.onopen = () => { status = 'connected'; };
|
|
291
|
+
|
|
292
|
+
es.onmessage = (event) => {
|
|
293
|
+
messages += event.data;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
es.onerror = () => {
|
|
297
|
+
status = es.readyState === EventSource.CLOSED ? 'closed' : 'reconnecting';
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
return () => es.close();
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
When `topic` changes, the effect re-runs: the previous EventSource is closed via cleanup, and a new one opens with the updated URL. The `$state` string accumulates tokens across messages, and Svelte updates the DOM after each assignment.
|
|
305
|
+
|
|
306
|
+
### Named Events
|
|
307
|
+
|
|
308
|
+
Servers can send named events (`event: delta`). Use `addEventListener` for these — `onmessage` only fires for unnamed events:
|
|
309
|
+
|
|
310
|
+
```js
|
|
311
|
+
$effect(() => {
|
|
312
|
+
const es = new EventSource('/api/completions');
|
|
313
|
+
|
|
314
|
+
es.addEventListener('delta', (e) => {
|
|
315
|
+
tokens += e.data;
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
es.addEventListener('done', () => {
|
|
319
|
+
es.close();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return () => es.close();
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### When to Use Raw EventSource
|
|
327
|
+
|
|
328
|
+
Use `@ai-sdk/svelte` (`useChat`, `useCompletion`) for AI SDK endpoints — the SDK manages the EventSource lifecycle, parsing, and error recovery internally. Use raw EventSource for custom SSE endpoints not backed by the AI SDK, or when fine-grained control over reconnection and event handling is required.
|