@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 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.