@elyracode/stack-silt 0.9.10
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/CHANGELOG.md +6 -0
- package/README.md +22 -0
- package/extensions/index.ts +10 -0
- package/package.json +38 -0
- package/skills/silt-stack/SKILL.md +390 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.9.10] - 2026-06-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- Initial release: SILT stack profile (Svelte 5, Inertia.js, Laravel, Tailwind CSS). Ships a deep `silt-stack` skill (Svelte 5 runes, the `@inertiajs/svelte` adapter, Laravel/Inertia controller and form patterns, TypeScript type sync, and common gotchas) and a `/silt:info` command.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @elyracode/stack-silt
|
|
2
|
+
|
|
3
|
+
Elyra stack profile for **SILT** (Svelte 5, Inertia.js, Laravel, Tailwind CSS).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
elyra install npm:@elyracode/stack-silt
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## What's included
|
|
12
|
+
|
|
13
|
+
- **Skills**: Deep SILT stack knowledge (Inertia.js protocol, Svelte 5 runes — `$props`/`$state`/`$derived`/`$effect`, `@inertiajs/svelte` adapter, Laravel controller patterns, `useForm`, TypeScript type sync, partial reloads, file structure conventions, common gotchas)
|
|
14
|
+
- **Commands**: `/silt:info` -- show stack profile status
|
|
15
|
+
|
|
16
|
+
## Notes
|
|
17
|
+
|
|
18
|
+
This profile targets **Svelte 5** (runes) with Inertia's `@inertiajs/svelte` adapter — plain Svelte driven by Laravel routing, not SvelteKit.
|
|
19
|
+
|
|
20
|
+
## Stack Detection
|
|
21
|
+
|
|
22
|
+
Elyra automatically detects SILT stack projects and suggests this package.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ExtensionAPI } from "@elyracode/coding-agent";
|
|
2
|
+
|
|
3
|
+
export default function (elyra: ExtensionAPI) {
|
|
4
|
+
elyra.registerCommand("silt:info", {
|
|
5
|
+
description: "Show detected SILT stack information",
|
|
6
|
+
handler: async (ctx) => {
|
|
7
|
+
ctx.ui.notify("info", "SILT stack profile loaded. Skills: silt-stack. Use /skill:silt-stack for full reference.");
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@elyracode/stack-silt",
|
|
3
|
+
"version": "0.9.10",
|
|
4
|
+
"description": "Elyra stack profile for SILT (Svelte 5, Inertia.js, Laravel, Tailwind CSS)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"elyra-package",
|
|
8
|
+
"silt",
|
|
9
|
+
"svelte",
|
|
10
|
+
"inertia",
|
|
11
|
+
"laravel",
|
|
12
|
+
"tailwind"
|
|
13
|
+
],
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"author": "Knut W. Horne",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/kwhorne/elyra.git",
|
|
19
|
+
"directory": "packages/stack-silt"
|
|
20
|
+
},
|
|
21
|
+
"elyra": {
|
|
22
|
+
"skills": [
|
|
23
|
+
"./skills"
|
|
24
|
+
],
|
|
25
|
+
"extensions": [
|
|
26
|
+
"./extensions/index.ts"
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
"peerDependencies": {
|
|
30
|
+
"@elyracode/coding-agent": "*",
|
|
31
|
+
"typebox": "*"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"clean": "echo 'nothing to clean'",
|
|
35
|
+
"build": "echo 'nothing to build'",
|
|
36
|
+
"check": "echo 'nothing to check'"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: silt-stack
|
|
3
|
+
description: Deep knowledge about the SILT stack - Svelte 5, Inertia.js, Laravel, and Tailwind CSS. Use when working on SILT stack projects, Inertia pages, Svelte 5 components with runes, Laravel controllers with Inertia responses, or TypeScript type sync between backend and frontend.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# SILT Stack Reference
|
|
7
|
+
|
|
8
|
+
## Architecture
|
|
9
|
+
|
|
10
|
+
The SILT stack connects:
|
|
11
|
+
- **Laravel** as the backend (routing, controllers, Eloquent, middleware, validation)
|
|
12
|
+
- **Inertia.js** as the glue layer (replaces traditional API + SPA routing)
|
|
13
|
+
- **Svelte 5** with runes (`$props`, `$state`, `$derived`, `$effect`) for the frontend
|
|
14
|
+
- **Tailwind CSS** for utility-first styling
|
|
15
|
+
|
|
16
|
+
### How Inertia Works
|
|
17
|
+
|
|
18
|
+
Inertia is NOT an API. It's a protocol:
|
|
19
|
+
1. First request: Server returns a full HTML page with the Svelte app + initial page data as JSON
|
|
20
|
+
2. Subsequent navigation: Inertia intercepts links, makes XHR requests, server returns only JSON props
|
|
21
|
+
3. Svelte swaps the page component without a full page reload
|
|
22
|
+
|
|
23
|
+
The server always controls routing. There is no client-side router (no SvelteKit routing, no `svelte-routing`).
|
|
24
|
+
|
|
25
|
+
## Laravel Side
|
|
26
|
+
|
|
27
|
+
### Controller Pattern
|
|
28
|
+
```php
|
|
29
|
+
use Inertia\Inertia;
|
|
30
|
+
use Inertia\Response;
|
|
31
|
+
|
|
32
|
+
class UserController extends Controller
|
|
33
|
+
{
|
|
34
|
+
public function index(): Response
|
|
35
|
+
{
|
|
36
|
+
return Inertia::render('Users/Index', [
|
|
37
|
+
'users' => User::query()
|
|
38
|
+
->select('id', 'name', 'email')
|
|
39
|
+
->paginate(10),
|
|
40
|
+
'filters' => request()->only(['search', 'role']),
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
public function create(): Response
|
|
45
|
+
{
|
|
46
|
+
return Inertia::render('Users/Create');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public function store(StoreUserRequest $request)
|
|
50
|
+
{
|
|
51
|
+
User::create($request->validated());
|
|
52
|
+
return redirect()->route('users.index')
|
|
53
|
+
->with('success', 'User created.');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public function edit(User $user): Response
|
|
57
|
+
{
|
|
58
|
+
return Inertia::render('Users/Edit', [
|
|
59
|
+
'user' => $user->only('id', 'name', 'email', 'role'),
|
|
60
|
+
]);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
public function update(UpdateUserRequest $request, User $user)
|
|
64
|
+
{
|
|
65
|
+
$user->update($request->validated());
|
|
66
|
+
return redirect()->route('users.index')
|
|
67
|
+
->with('success', 'User updated.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public function destroy(User $user)
|
|
71
|
+
{
|
|
72
|
+
$user->delete();
|
|
73
|
+
return redirect()->route('users.index')
|
|
74
|
+
->with('success', 'User deleted.');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Shared Data (available on every page)
|
|
80
|
+
```php
|
|
81
|
+
// app/Http/Middleware/HandleInertiaRequests.php
|
|
82
|
+
public function share(Request $request): array
|
|
83
|
+
{
|
|
84
|
+
return [
|
|
85
|
+
...parent::share($request),
|
|
86
|
+
'auth' => [
|
|
87
|
+
'user' => $request->user()?->only('id', 'name', 'email', 'role'),
|
|
88
|
+
],
|
|
89
|
+
'flash' => [
|
|
90
|
+
'success' => fn () => $request->session()->get('success'),
|
|
91
|
+
'error' => fn () => $request->session()->get('error'),
|
|
92
|
+
],
|
|
93
|
+
];
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Lazy Props (only loaded when needed)
|
|
98
|
+
```php
|
|
99
|
+
return Inertia::render('Users/Show', [
|
|
100
|
+
'user' => $user,
|
|
101
|
+
'activity' => Inertia::lazy(fn () => $user->activity()->latest()->get()),
|
|
102
|
+
]);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Partial Reloads
|
|
106
|
+
```php
|
|
107
|
+
// Only reload specific props
|
|
108
|
+
return Inertia::render('Dashboard', [
|
|
109
|
+
'stats' => fn () => Stats::calculate(),
|
|
110
|
+
'notifications' => fn () => auth()->user()->unreadNotifications,
|
|
111
|
+
]);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Svelte 5 Side
|
|
115
|
+
|
|
116
|
+
The Inertia adapter is `@inertiajs/svelte`. Page components receive their props directly — read them with the `$props()` rune.
|
|
117
|
+
|
|
118
|
+
### Page Component Pattern
|
|
119
|
+
```svelte
|
|
120
|
+
<script lang="ts">
|
|
121
|
+
import { Link, router } from '@inertiajs/svelte'
|
|
122
|
+
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.svelte'
|
|
123
|
+
|
|
124
|
+
interface User {
|
|
125
|
+
id: number
|
|
126
|
+
name: string
|
|
127
|
+
email: string
|
|
128
|
+
role: string
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface Props {
|
|
132
|
+
users: {
|
|
133
|
+
data: User[]
|
|
134
|
+
links: Array<{ url: string | null; label: string; active: boolean }>
|
|
135
|
+
current_page: number
|
|
136
|
+
last_page: number
|
|
137
|
+
}
|
|
138
|
+
filters: {
|
|
139
|
+
search?: string
|
|
140
|
+
role?: string
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let { users, filters }: Props = $props()
|
|
145
|
+
|
|
146
|
+
function destroy(id: number) {
|
|
147
|
+
if (confirm('Are you sure?')) {
|
|
148
|
+
router.delete(route('users.destroy', id))
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
</script>
|
|
152
|
+
|
|
153
|
+
<svelte:head><title>Users</title></svelte:head>
|
|
154
|
+
|
|
155
|
+
<AuthenticatedLayout>
|
|
156
|
+
<div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
|
157
|
+
<div class="flex justify-between items-center mb-6">
|
|
158
|
+
<h1 class="text-2xl font-semibold text-gray-900 dark:text-white">Users</h1>
|
|
159
|
+
<Link href={route('users.create')} class="btn btn-primary">Add User</Link>
|
|
160
|
+
</div>
|
|
161
|
+
|
|
162
|
+
<div class="bg-white dark:bg-gray-800 shadow rounded-lg overflow-hidden">
|
|
163
|
+
<table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
|
|
164
|
+
<thead class="bg-gray-50 dark:bg-gray-900">
|
|
165
|
+
<tr>
|
|
166
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Name</th>
|
|
167
|
+
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Email</th>
|
|
168
|
+
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Actions</th>
|
|
169
|
+
</tr>
|
|
170
|
+
</thead>
|
|
171
|
+
<tbody class="divide-y divide-gray-200 dark:divide-gray-700">
|
|
172
|
+
{#each users.data as user (user.id)}
|
|
173
|
+
<tr>
|
|
174
|
+
<td class="px-6 py-4 whitespace-nowrap">{user.name}</td>
|
|
175
|
+
<td class="px-6 py-4 whitespace-nowrap">{user.email}</td>
|
|
176
|
+
<td class="px-6 py-4 whitespace-nowrap text-right">
|
|
177
|
+
<Link href={route('users.edit', user.id)} class="text-blue-600 hover:text-blue-900">Edit</Link>
|
|
178
|
+
<button onclick={() => destroy(user.id)} class="ml-4 text-red-600 hover:text-red-900">Delete</button>
|
|
179
|
+
</td>
|
|
180
|
+
</tr>
|
|
181
|
+
{/each}
|
|
182
|
+
</tbody>
|
|
183
|
+
</table>
|
|
184
|
+
</div>
|
|
185
|
+
</div>
|
|
186
|
+
</AuthenticatedLayout>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Runes (Svelte 5 reactivity)
|
|
190
|
+
```svelte
|
|
191
|
+
<script lang="ts">
|
|
192
|
+
// Props (replaces `export let`)
|
|
193
|
+
let { count = 0, label }: { count?: number; label: string } = $props()
|
|
194
|
+
|
|
195
|
+
// Local reactive state (replaces top-level `let`)
|
|
196
|
+
let quantity = $state(1)
|
|
197
|
+
|
|
198
|
+
// Derived values (replaces `$:`)
|
|
199
|
+
let total = $derived(quantity * count)
|
|
200
|
+
|
|
201
|
+
// Side effects (replaces `$: { ... }` blocks)
|
|
202
|
+
$effect(() => {
|
|
203
|
+
console.log('quantity changed to', quantity)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
// Two-way bindable prop (replaces `export let` + bind)
|
|
207
|
+
let { value = $bindable('') }: { value?: string } = $props()
|
|
208
|
+
</script>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Form Handling with useForm
|
|
212
|
+
`useForm` from the Svelte adapter returns a store. Access fields with the `$` prefix and `bind:value`.
|
|
213
|
+
```svelte
|
|
214
|
+
<script lang="ts">
|
|
215
|
+
import { useForm } from '@inertiajs/svelte'
|
|
216
|
+
|
|
217
|
+
const form = useForm({
|
|
218
|
+
name: '',
|
|
219
|
+
email: '',
|
|
220
|
+
role: 'user',
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
function submit() {
|
|
224
|
+
$form.post(route('users.store'), {
|
|
225
|
+
onSuccess: () => $form.reset(),
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
</script>
|
|
229
|
+
|
|
230
|
+
<form onsubmit={(e) => { e.preventDefault(); submit() }} class="space-y-4">
|
|
231
|
+
<div>
|
|
232
|
+
<label class="block text-sm font-medium text-gray-700">Name</label>
|
|
233
|
+
<input bind:value={$form.name} type="text" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm" />
|
|
234
|
+
{#if $form.errors.name}<p class="mt-1 text-sm text-red-600">{$form.errors.name}</p>{/if}
|
|
235
|
+
</div>
|
|
236
|
+
<div>
|
|
237
|
+
<label class="block text-sm font-medium text-gray-700">Email</label>
|
|
238
|
+
<input bind:value={$form.email} type="email" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm" />
|
|
239
|
+
{#if $form.errors.email}<p class="mt-1 text-sm text-red-600">{$form.errors.email}</p>{/if}
|
|
240
|
+
</div>
|
|
241
|
+
<button type="submit" disabled={$form.processing} class="btn btn-primary">
|
|
242
|
+
{$form.processing ? 'Saving...' : 'Save'}
|
|
243
|
+
</button>
|
|
244
|
+
</form>
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Accessing Shared Page Data
|
|
248
|
+
```svelte
|
|
249
|
+
<script lang="ts">
|
|
250
|
+
import { page } from '@inertiajs/svelte'
|
|
251
|
+
// `page` is a store: read with $page
|
|
252
|
+
</script>
|
|
253
|
+
|
|
254
|
+
<p>Logged in as {$page.props.auth.user.name}</p>
|
|
255
|
+
{#if $page.props.flash.success}
|
|
256
|
+
<div class="alert">{$page.props.flash.success}</div>
|
|
257
|
+
{/if}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### TypeScript Type Definitions
|
|
261
|
+
```typescript
|
|
262
|
+
// resources/js/types/index.d.ts
|
|
263
|
+
export interface User {
|
|
264
|
+
id: number
|
|
265
|
+
name: string
|
|
266
|
+
email: string
|
|
267
|
+
email_verified_at?: string
|
|
268
|
+
role: 'admin' | 'user'
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export type PageProps<T extends Record<string, unknown> = Record<string, unknown>> = T & {
|
|
272
|
+
auth: {
|
|
273
|
+
user: User
|
|
274
|
+
}
|
|
275
|
+
flash: {
|
|
276
|
+
success?: string
|
|
277
|
+
error?: string
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
### Inertia Router Methods
|
|
283
|
+
```typescript
|
|
284
|
+
import { router } from '@inertiajs/svelte'
|
|
285
|
+
|
|
286
|
+
// Navigate
|
|
287
|
+
router.visit('/users')
|
|
288
|
+
router.get('/users')
|
|
289
|
+
router.post('/users', data)
|
|
290
|
+
router.put('/users/1', data)
|
|
291
|
+
router.patch('/users/1', data)
|
|
292
|
+
router.delete('/users/1')
|
|
293
|
+
|
|
294
|
+
// With options
|
|
295
|
+
router.post('/users', data, {
|
|
296
|
+
preserveScroll: true,
|
|
297
|
+
preserveState: true,
|
|
298
|
+
only: ['users'], // Partial reload
|
|
299
|
+
onSuccess: () => {},
|
|
300
|
+
onError: (errors) => {},
|
|
301
|
+
onFinish: () => {},
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
// Reload current page props
|
|
305
|
+
router.reload({ only: ['notifications'] })
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Links
|
|
309
|
+
Use the `<Link>` component, or the `inertia` action on a plain anchor:
|
|
310
|
+
```svelte
|
|
311
|
+
<script>
|
|
312
|
+
import { Link, inertia } from '@inertiajs/svelte'
|
|
313
|
+
</script>
|
|
314
|
+
|
|
315
|
+
<Link href={route('users.index')}>Users</Link>
|
|
316
|
+
<a use:inertia href={route('users.create')}>New User</a>
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## File Structure Conventions
|
|
320
|
+
|
|
321
|
+
```
|
|
322
|
+
app/
|
|
323
|
+
Http/
|
|
324
|
+
Controllers/ # Controllers return Inertia::render()
|
|
325
|
+
Middleware/
|
|
326
|
+
HandleInertiaRequests.php # Shared data
|
|
327
|
+
Requests/ # Form validation
|
|
328
|
+
Models/
|
|
329
|
+
resources/
|
|
330
|
+
js/
|
|
331
|
+
Pages/ # Inertia page components (maps to Inertia::render paths)
|
|
332
|
+
Users/
|
|
333
|
+
Index.svelte
|
|
334
|
+
Create.svelte
|
|
335
|
+
Edit.svelte
|
|
336
|
+
Dashboard.svelte
|
|
337
|
+
Components/ # Reusable Svelte components
|
|
338
|
+
Layouts/ # Layout components
|
|
339
|
+
AuthenticatedLayout.svelte
|
|
340
|
+
GuestLayout.svelte
|
|
341
|
+
lib/ # Shared helpers, stores, composables
|
|
342
|
+
types/ # TypeScript type definitions
|
|
343
|
+
index.d.ts
|
|
344
|
+
app.ts # Svelte app bootstrap + Inertia setup
|
|
345
|
+
routes/
|
|
346
|
+
web.php # All routes (Inertia handles them server-side)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### App Bootstrap (app.ts)
|
|
350
|
+
```typescript
|
|
351
|
+
import { createInertiaApp } from '@inertiajs/svelte'
|
|
352
|
+
import { mount } from 'svelte'
|
|
353
|
+
|
|
354
|
+
createInertiaApp({
|
|
355
|
+
resolve: (name) => {
|
|
356
|
+
const pages = import.meta.glob('./Pages/**/*.svelte', { eager: true })
|
|
357
|
+
return pages[`./Pages/${name}.svelte`]
|
|
358
|
+
},
|
|
359
|
+
setup({ el, App, props }) {
|
|
360
|
+
mount(App, { target: el, props })
|
|
361
|
+
},
|
|
362
|
+
})
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Type Sync Pattern (Backend to Frontend)
|
|
366
|
+
|
|
367
|
+
When changing data sent from Laravel to Svelte, update both sides:
|
|
368
|
+
|
|
369
|
+
1. **Laravel controller** -- change what's passed to `Inertia::render()`
|
|
370
|
+
2. **TypeScript types** -- update the interface in `resources/js/types/`
|
|
371
|
+
3. **Svelte component** -- update the `$props()` type to match
|
|
372
|
+
|
|
373
|
+
Example: Adding a `phone` field to User:
|
|
374
|
+
1. Migration: `$table->string('phone')->nullable()`
|
|
375
|
+
2. Controller: add `'phone'` to the select/only list
|
|
376
|
+
3. TypeScript: add `phone?: string` to `User` interface
|
|
377
|
+
4. Svelte: add the input field bound to the prop
|
|
378
|
+
|
|
379
|
+
## Common Gotchas
|
|
380
|
+
|
|
381
|
+
1. **No client-side router**: Inertia replaces routing. Use `<Link>`/`use:inertia` and `router` from `@inertiajs/svelte`. This is plain Svelte, NOT SvelteKit — there is no `+page.svelte`, no `load` functions, no file-based routing.
|
|
382
|
+
2. **Runes, not Svelte 4 syntax**: Use `$props()` not `export let`, `$state()` not bare `let` for reactive state, `$derived()` not `$:`, `onclick` not `on:click`. Svelte 5 is the target.
|
|
383
|
+
3. **Stores need the `$` prefix**: `page`, and the result of `useForm`, are stores. Read them as `$page`, `$form`. Forgetting the `$` is the most common SILT bug.
|
|
384
|
+
4. **Validation errors**: Laravel validation errors are available via `$form.errors` or `$page.props.errors`. No manual error handling needed.
|
|
385
|
+
5. **Flash messages**: Use Laravel's `->with('success', 'msg')` on redirects. Access via shared data in `HandleInertiaRequests.php`, read as `$page.props.flash.success`.
|
|
386
|
+
6. **File uploads**: Use `$form.post()` with File values. Inertia handles multipart automatically.
|
|
387
|
+
7. **SSR**: Inertia supports SSR with `@inertiajs/svelte`. Configure in `vite.config.ts` with the `laravel-vite-plugin`, and use `render` from the adapter in `ssr.ts`.
|
|
388
|
+
8. **Ziggy routes**: Use `route('name')` via the Ziggy package. Never hardcode URLs.
|
|
389
|
+
9. **Partial reloads**: Wrap expensive props in closures `fn () =>` in the controller. Request them with `router.reload({ only: ['prop'] })`.
|
|
390
|
+
10. **Mounting**: Svelte 5 uses `mount()` from `svelte`, not `new App()`. The Inertia setup in `app.ts` reflects this.
|