Elyra · · 4 min read

PHP Gets a Real Brain, and Svelte Joins the Laravel Party - Elyra v0.9.10

PHP Gets a Real Brain, and Svelte Joins the Laravel Party - Elyra v0.9.10

Most releases are a pile of bullet points. This one has a theme: Elyra now understands more of your stack instead of just reading it. Two of the three headline changes are about giving the agent real, semantic knowledge of code it used to treat as plain text. Let me walk you through why that matters and how to actually use it.

Why: grep is not understanding

Here's the uncomfortable truth about most coding agents. When they "look up" where a function is defined, they're usually just running a fancy text search. That works until it doesn't:

  • Two classes have a method called handle(). Which one did you mean?

  • A Laravel facade resolves to a class that's never named in your code.

  • A trait is mixed into 14 models. "Find references" with grep finds the trait, not the 14 places it actually matters.

For TypeScript, Elyra already solved this with a language server (@elyracode/lsp-typescript). For PHP — half the Laravel world — the agent was still squinting at text.

v0.9.10 fixes that.

PHP/Laravel LSP: the agent now navigates symbols, not strings

The new @elyracode/lsp-php extension starts a real PHP language server in the background — Intelephense, with a phpactor fallback — the moment it sees a composer.json. From then on the agent has four new tools:

  • php_definitions — jump to where a symbol is actually defined

  • php_references — find every real usage, not every string match

  • php_diagnostics — see type errors and undefined methods like your editor does

  • php_hover — read signatures and docblocks inline

How: it just works

There's no config to write. Open a Laravel project, and:

You: Where is the password reset actually handled?

Elyra: (calls php_definitions on the route action) → app/Http/Controllers/Auth/PasswordResetController.php:42 The route 'password.update' resolves to PasswordResetController@update, which dispatches PasswordReset event handled by SendPasswordResetNotification.

That chain — route to controller to event to listener — is exactly the kind of thing grep gets lost in. The LSP follows it because it understands PHP's resolution rules.

A concrete refactor example

Say you want to rename a model method scopeActive() to scopeEnabled():

You: Rename scopeActive to scopeEnabled on the Subscription model
and update every call site.

Elyra: (php_references on scopeActive) Found 6 references:

  • Subscription::active() called as ->active() in 5 places
  • 1 in a Blade-adjacent query builder chain Renaming the scope and all 6 call sites...

Because Laravel turns scopeActive into the ->active() query method, a naive search for scopeActive would find one spot and miss the five that matter. The language server knows about the scope-to-method magic. The agent doesn't break your app.

SILT: Svelte 5 + Inertia + Laravel + Tailwind, as a first-class stack

Elyra already knew TALL, VILT, and RILT. The fast-growing combo it was missing: SILT — Svelte 5 on the front, Laravel on the back, Inertia gluing them, Tailwind painting them.

Why a "stack profile" beats generic knowledge

A model knows Svelte and Laravel in general. What trips it up is the seam:

  • Svelte 5 runes ($state, $derived, $props) instead of the old export let

  • The @inertiajs/svelte adapter, which is subtly different from the Vue or React one

  • How a Laravel controller's Inertia::render() props line up with your Svelte page props

  • Keeping TypeScript types in sync across the PHP/JS boundary

The new @elyracode/stack-silt extension ships a deep silt-stack skill that encodes all of that, plus the gotchas people hit on day one.

How: detection is automatic

Run /init in a SILT project and Elyra now recognizes it:

/init

Detected stack: SILT (Svelte 5 + Inertia + Laravel + Tailwind) Suggested extension: @elyracode/stack-silt Wrote .elyra/AGENTS.md with stack context.

Then, when you ask for a feature, you get idiomatic Svelte 5 — not the export let patterns a model defaults to from years of old training data:

<!-- resources/js/Pages/Projects/Edit.svelte -->
<script lang="ts">
import { useForm } from '@inertiajs/svelte'
let { project } = $props()
const form = useForm({ name: project.name })
</script>

<form onsubmit={(e) => { e.preventDefault(); $form.put(/projects/${project.id}) }}> <input bind:value={$form.name} /> </form>

Note the $props() rune and the $form store access — the things a generic answer gets wrong. And it pairs the controller side to match:

// app/Http/Controllers/ProjectController.php
public function update(Request $request, Project $project)
{
$project->update($request->validate(['name' => 'required|string|max:255']));
return back();
}

Want the stack reference on demand? /silt:info prints the cheat sheet.

TypeScript 6 alignment

Quieter, but worth a line: the whole workspace is aligned to TypeScript 6. If you build extensions against Elyra's packages, you're on the current compiler with everyone else — no version-skew surprises.

The bigger picture

There's a thread connecting these. Elyra is moving from reading your code to understanding it. The TypeScript LSP proved the pattern; PHP now gets the same treatment; SILT gives the agent the mental model for a stack it used to fake. Less guessing, fewer broken refactors, more answers that are right the first time.

Getting it

# PHP projects (needs Intelephense or phpactor available)
elyra ext add @elyracode/lsp-php

Svelte 5 + Inertia + Laravel projects

elyra ext add @elyracode/stack-silt

Or just run /init and let Elyra suggest what fits.