Available since v0.1.0
Livewire
StableLivewire is Laravel's full-stack reactivity framework. It re-renders server-side templates on every user action and patches the DOM client-side via morphdom. fs-* attributes live directly in your Blade templates.
Install
Load the agent in your layout (resources/views/layouts/app.blade.php):
<head>
<script src="{{ asset('js/faultsense-agent.min.js') }}" defer></script>
@livewireScripts
<script>
document.addEventListener('DOMContentLoaded', () => {
Faultsense.init({
releaseLabel: '{{ config('app.release_label') }}',
collectorURL: '/events',
apiKey: '{{ env('FAULTSENSE_KEY') }}',
});
});
</script>
</head>
Morph swaps — use updated
Livewire uses morphdom for all DOM updates. Elements preserve identity across re-renders — attributes and children mutate in place. This is PAT-04 morphdom preserved-identity.
The canonical assertion type for Livewire mutations is fs-assert-updated, not added. An item is not "added" — it's an in-place patch that makes the existing element look different.
<button
wire:click="increment"
fs-assert="counter/increment"
fs-trigger="click"
fs-assert-updated="#count[text-matches=\d+]">
+
</button>
<span id="count">{{ $count }}</span>
Clicking the button triggers Livewire, which re-renders the component server-side and morphs the response into the DOM. #count's text changes in place — updated with text-matches catches it.
wire:model and form controls
wire:model binds a form control to a server-side property. The update fires on blur or change by default.
<input type="text"
wire:model="search"
fs-assert="search/filter"
fs-trigger="blur"
fs-assert-updated=".results[count-min=0]" />
Actions and DOM mutations
wire:click fires an action on the server; the server re-renders and returns HTML; Livewire morphs it in. Track actions via wire:click + fs-trigger="click":
<button
wire:click="deleteTodo({{ $todo->id }})"
fs-assert="todos/delete"
fs-trigger="click"
fs-assert-removed=".todo-item">
Delete
</button>
Under morphdom, a deletion removes the element from the morph tree — removed fires correctly.
Dynamic assertion values
Blade's expression syntax works the same as ERB/EJS for computing the expected next state:
<input type="checkbox"
wire:click="toggle({{ $todo->id }})"
fs-assert="todos/toggle"
fs-trigger="change"
fs-assert-updated=".todo-item[classlist=completed:{{ !$todo->completed ? 'true' : 'false' }}]" />
Gotchas
- Use
updated, notadded, for item mutations. Livewire morphs — identity is preserved.fs-assert-added=".todo-item"will never match on a state toggle. wire:keymatters. Livewire useswire:keyto match elements during morph. Without stable keys on list items, morphdom may reorder and surprise your assertions. Always setwire:key="{{ $todo->id }}"on list items.- Full component re-renders can produce large mutation batches. The agent's PAT-07 microtask batching handles this — every record is fanned out through the resolver.
- Alpine inside Livewire. Livewire ships with Alpine. Use Alpine's CSP build for strict-CSP production environments.