# FuelFinder Sub-Component Split Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Split the monolithic `FuelFinder` Livewire component into four focused sub-components (`Search`, `Map`, `StationList`, `Recommendation`) communicating via a `stations-found` browser event, so each part re-renders independently without re-mounting Leaflet. **Architecture:** `FuelFinder` becomes a layout-only shell. `Search` owns all API calls and dispatches `stations-found` with results, meta, prediction, and radius. `Map` relays the event to Alpine/Leaflet via `map-update`. `StationList` and `Recommendation` each listen for `stations-found` and re-render their slice of the UI. `Forecast` is unchanged. **Tech Stack:** Laravel 13, Livewire 4, Alpine.js, Leaflet.js, Pest 4 --- ## File Map **New PHP classes:** - `app/Livewire/Public/Fuel/Search.php` — search state, API calls, dispatcher - `app/Livewire/Public/Fuel/Map.php` — event relay, no state - `app/Livewire/Public/Fuel/StationList.php` — station card list - `app/Livewire/Public/Fuel/Recommendation.php` — prediction card **New Blade views:** - `resources/views/livewire/public/fuel/search.blade.php` - `resources/views/livewire/public/fuel/map.blade.php` - `resources/views/livewire/public/fuel/station-list.blade.php` - `resources/views/livewire/public/fuel/recommendation.blade.php` **New tests:** - `tests/Feature/Livewire/Fuel/SearchTest.php` - `tests/Feature/Livewire/Fuel/StationListTest.php` - `tests/Feature/Livewire/Fuel/RecommendationTest.php` - `tests/Feature/Livewire/Fuel/MapTest.php` **Modified files:** - `app/Livewire/Public/FuelFinder.php` — strip all state and methods - `resources/views/livewire/public/fuel-finder.blade.php` — replace sections with `` tags - `resources/js/maps/station-map.js` — replace `$watch` with `map-update` window event listener - `resources/views/components/fuel/station-map.blade.php` — remove `@entangle` props - `tests/Feature/Livewire/FuelFinderTest.php` — strip to render-only test --- ## Task 1: fuel.search component **Files:** - Create: `app/Livewire/Public/Fuel/Search.php` - Create: `resources/views/livewire/public/fuel/search.blade.php` - Create: `tests/Feature/Livewire/Fuel/SearchTest.php` - [ ] **Step 1: Scaffold the component and test** ```bash php artisan make:livewire Public/Fuel/Search --no-interaction php artisan make:test Feature/Livewire/Fuel/SearchTest --pest --no-interaction ``` - [ ] **Step 2: Write the failing tests** Replace the contents of `tests/Feature/Livewire/Fuel/SearchTest.php`: ```php assertStatus(200) ->assertSeeHtml('name="search"'); }); it('has default property values', function () { Livewire::test(Search::class) ->assertSet('search', '') ->assertSet('fuelType', 'petrol') ->assertSet('radius', 5) ->assertSet('sort', 'reliable') ->assertSet('apiError', null) ->assertSet('hasSearched', false); }); it('validates search is required', function () { Livewire::test(Search::class) ->call('findStations') ->assertHasErrors(['search' => 'required']); }); it('validates fuelType is required', function () { Livewire::test(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', '') ->call('findStations') ->assertHasErrors(['fuelType' => 'required']); }); it('dispatches stations-found with results, meta, prediction and radius on successful search', function () { Http::fake([ '*/api/stations*' => Http::response([ 'data' => [ [ 'station_id' => 'abc123', 'name' => 'BP Garage', 'brand' => 'BP', 'is_supermarket' => false, 'address' => '1 High Street', 'postcode' => 'SW1A 1AA', 'lat' => 51.5074, 'lng' => -0.1278, 'distance_km' => 1.5, 'fuel_type' => 'e10', 'price_pence' => 14390, 'price' => 143.9, 'price_updated_at' => '2026-04-05T08:00:00.000Z', 'price_classification' => 'current', 'price_classification_label' => 'Current', ], ], 'meta' => ['count' => 1, 'lowest_pence' => 14390, 'avg_pence' => 14390.0], ], 200), '*/api/prediction*' => Http::response([ 'action' => 'fill_now', 'confidence_score' => 80.0, 'confidence_label' => 'high', 'reasoning' => 'Prices rising.', 'predicted_direction' => 'up', 'predicted_change_pence' => 3.5, ], 200), ]); Livewire::test(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('hasSearched', true) ->assertSet('apiError', null) ->assertDispatched('stations-found', fn ($event, $params) => count($params['results']) === 1 && $params['results'][0]['name'] === 'BP Garage' && $params['meta']['count'] === 1 && $params['prediction']['action'] === 'fill_now' && $params['radius'] === 5 ); }); it('sets apiError from 422 station response and does not dispatch stations-found', function () { Http::fake([ '*/api/stations*' => Http::response([ 'errors' => ['postcode' => ['Postcode not found.']], ], 422), ]); Livewire::test(Search::class) ->set('search', 'ZZ99 9ZZ') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('hasSearched', false) ->assertSet('apiError', 'Postcode not found.') ->assertNotDispatched('stations-found'); }); it('sets generic apiError on server error', function () { Http::fake([ '*/api/stations*' => Http::response([], 500), ]); Livewire::test(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('apiError', 'Unable to fetch stations. Please try again.'); }); it('converts radius from miles to km in the outgoing stations request', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->set('radius', 5) ->call('findStations'); Http::assertSent(function ($request) { if (! str_contains($request->url(), 'api/stations')) { return false; } $data = $request->data(); return isset($data['radius']) && abs((float) $data['radius'] - 8.05) < 0.01; }); }); it('resets apiError before each new search', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->set('apiError', 'Old error') ->call('findStations') ->assertSet('apiError', null); }); it('does not call findStations on updatedFuelType if not yet searched', function () { Http::fake(); Livewire::test(Search::class) ->set('fuelType', 'diesel'); Http::assertNothingSent(); }); it('re-runs findStations on updatedFuelType when already searched', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(Search::class) ->set('hasSearched', true) ->set('search', 'SW1A 1AA') ->set('fuelType', 'diesel'); Http::assertSentCount(2); }); it('re-runs findStations on updatedRadius when already searched', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(Search::class) ->set('hasSearched', true) ->set('search', 'SW1A 1AA') ->set('radius', 10); Http::assertSentCount(2); }); it('re-runs findStations on updatedSort when already searched', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response(['action' => 'no_signal', 'confidence_score' => 0, 'confidence_label' => 'low', 'reasoning' => '', 'predicted_direction' => 'stable', 'predicted_change_pence' => 0], 200), ]); Livewire::test(Search::class) ->set('hasSearched', true) ->set('search', 'SW1A 1AA') ->set('sort', 'price'); Http::assertSentCount(2); }); it('prediction is null in stations-found payload when prediction api fails', function () { Http::fake([ '*/api/stations*' => Http::response(['data' => [], 'meta' => ['count' => 0]], 200), '*/api/prediction*' => Http::response([], 500), ]); Livewire::test(Search::class) ->set('search', 'SW1A 1AA') ->set('fuelType', 'petrol') ->call('findStations') ->assertSet('hasSearched', true) ->assertDispatched('stations-found', fn ($event, $params) => $params['prediction'] === null ); }); ``` - [ ] **Step 3: Run tests to confirm they fail** ```bash php artisan test --compact --filter="SearchTest" --timeout=10 ``` Expected: all fail with `Class "App\Livewire\Public\Fuel\Search" not found` or similar. - [ ] **Step 4: Implement Search.php** Replace the contents of `app/Livewire/Public/Fuel/Search.php`: ```php hasSearched) { $this->findStations(); } } public function updatedRadius(): void { if ($this->hasSearched) { $this->findStations(); } } public function updatedSort(): void { if ($this->hasSearched) { $this->findStations(); } } public function findStations(): void { $this->validate(); $this->apiError = null; $this->hasSearched = false; $radiusKm = round($this->radius * 1.60934, 2); try { $response = Http::timeout(10) ->withHeaders(['X-Api-Key' => config('app.api_secret_key')]) ->get(url('/api/stations'), [ 'postcode' => $this->search, 'fuel_type' => $this->fuelType, 'radius' => $radiusKm, 'sort' => $this->sort, ]); } catch (ConnectionException) { $this->apiError = 'Unable to fetch stations. Please try again.'; return; } if ($response->status() === 422) { $errors = $response->json('errors', []); $this->apiError = collect($errors)->flatten()->first() ?? $response->json('message', 'Validation error.'); return; } if (! $response->successful()) { $this->apiError = 'Unable to fetch stations. Please try again.'; return; } $results = $response->json('data', []); $meta = $response->json('meta', []); $this->hasSearched = true; $prediction = null; try { $predictionResponse = Http::timeout(10) ->withHeaders(['X-Api-Key' => config('app.api_secret_key')]) ->get(url('/api/prediction')); if ($predictionResponse->successful()) { $prediction = $predictionResponse->json(); } } catch (ConnectionException) { // Prediction failure is silent — stations are more important } $this->dispatch('stations-found', results: $results, meta: $meta, prediction: $prediction, radius: $this->radius, ); } public function render(): View { return view('livewire.public.fuel.search'); } } ``` - [ ] **Step 5: Implement search.blade.php** Replace the contents of `resources/views/livewire/public/fuel/search.blade.php`: ```blade
{{-- IP fallback nudge --}}

Showing approximate location. Enter your postcode above for exact results.

@error('search')

{{ $message }}

@enderror
@if ($apiError)
{{ $apiError }}
@endif
``` - [ ] **Step 6: Run tests to confirm they pass** ```bash php artisan test --compact --filter="SearchTest" --timeout=10 ``` Expected: all 13 tests pass. - [ ] **Step 7: Run Pint** ```bash vendor/bin/pint app/Livewire/Public/Fuel/Search.php --format agent ``` - [ ] **Step 8: Commit** ```bash git add app/Livewire/Public/Fuel/Search.php resources/views/livewire/public/fuel/search.blade.php tests/Feature/Livewire/Fuel/SearchTest.php git commit -m "feat: extract fuel.search Livewire component with stations-found dispatch" ``` --- ## Task 2: fuel.station-list component **Files:** - Create: `app/Livewire/Public/Fuel/StationList.php` - Create: `resources/views/livewire/public/fuel/station-list.blade.php` - Create: `tests/Feature/Livewire/Fuel/StationListTest.php` - [ ] **Step 1: Scaffold** ```bash php artisan make:livewire Public/Fuel/StationList --no-interaction php artisan make:test Feature/Livewire/Fuel/StationListTest --pest --no-interaction ``` - [ ] **Step 2: Write the failing tests** Replace contents of `tests/Feature/Livewire/Fuel/StationListTest.php`: ```php assertStatus(200) ->assertSet('hasSearched', false) ->assertDontSee('Stations Nearby'); }); it('shows station cards after stations-found event', function () { $station = [ 'station_id' => 'abc123', 'name' => 'BP Garage', 'brand' => 'BP', 'is_supermarket' => false, 'address' => '1 High Street', 'postcode' => 'SW1A 1AA', 'lat' => 51.5074, 'lng' => -0.1278, 'distance_km' => 1.5, 'fuel_type' => 'e10', 'price_pence' => 14390, 'price' => 143.9, 'price_updated_at' => '2026-04-05T08:00:00.000Z', 'price_classification' => 'current', 'price_classification_label' => 'Current', ]; $meta = ['count' => 1, 'lowest_pence' => 14390, 'avg_pence' => 14390.0]; Livewire::test(StationList::class) ->dispatch('stations-found', results: [$station], meta: $meta, prediction: null, radius: 5) ->assertSet('hasSearched', true) ->assertSee('Stations Nearby') ->assertSee('BP Garage') ->assertSee('1 Result'); }); it('shows empty state message when stations-found has no results', function () { Livewire::test(StationList::class) ->set('search', 'ZZ99 9ZZ') ->dispatch('stations-found', results: [], meta: ['count' => 0], prediction: null, radius: 5) ->assertSet('hasSearched', true) ->assertSee('No stations found'); }); it('updates results when stations-found fires again', function () { $station = [ 'station_id' => 'abc123', 'name' => 'BP Garage', 'brand' => 'BP', 'is_supermarket' => false, 'address' => '1 High Street', 'postcode' => 'SW1A 1AA', 'lat' => 51.5074, 'lng' => -0.1278, 'distance_km' => 1.5, 'fuel_type' => 'e10', 'price_pence' => 14390, 'price' => 143.9, 'price_updated_at' => '2026-04-05T08:00:00.000Z', 'price_classification' => 'current', 'price_classification_label' => 'Current', ]; Livewire::test(StationList::class) ->dispatch('stations-found', results: [$station], meta: ['count' => 1], prediction: null, radius: 5) ->assertSee('BP Garage') ->dispatch('stations-found', results: [], meta: ['count' => 0], prediction: null, radius: 5) ->assertDontSee('BP Garage'); }); ``` - [ ] **Step 3: Run tests to confirm they fail** ```bash php artisan test --compact --filter="StationListTest" --timeout=10 ``` Expected: all fail — class not found or component has no `handle` method yet. - [ ] **Step 4: Implement StationList.php** Replace contents of `app/Livewire/Public/Fuel/StationList.php`: ```php results = $results; $this->meta = $meta; $this->radius = $radius; $this->hasSearched = true; } public function render(): View { return view('livewire.public.fuel.station-list'); } } ``` - [ ] **Step 5: Implement station-list.blade.php** Replace contents of `resources/views/livewire/public/fuel/station-list.blade.php`: ```blade
@if ($hasSearched)
@if (! empty($meta))

Stations Nearby

{{ $meta['count'] ?? 0 }} {{ str('Result')->plural($meta['count'] ?? 0) }}
@endif @forelse ($results as $station)
@empty

No stations found within {{ $radius }} {{ str('mile')->plural($radius) }} of "{{ $search }}".

@endforelse
@endif
``` - [ ] **Step 6: Run tests to confirm they pass** ```bash php artisan test --compact --filter="StationListTest" --timeout=10 ``` Expected: all 4 tests pass. - [ ] **Step 7: Run Pint** ```bash vendor/bin/pint app/Livewire/Public/Fuel/StationList.php --format agent ``` - [ ] **Step 8: Commit** ```bash git add app/Livewire/Public/Fuel/StationList.php resources/views/livewire/public/fuel/station-list.blade.php tests/Feature/Livewire/Fuel/StationListTest.php git commit -m "feat: extract fuel.station-list Livewire component" ``` --- ## Task 3: fuel.recommendation component **Files:** - Create: `app/Livewire/Public/Fuel/Recommendation.php` - Create: `resources/views/livewire/public/fuel/recommendation.blade.php` - Create: `tests/Feature/Livewire/Fuel/RecommendationTest.php` - [ ] **Step 1: Scaffold** ```bash php artisan make:livewire Public/Fuel/Recommendation --no-interaction php artisan make:test Feature/Livewire/Fuel/RecommendationTest --pest --no-interaction ``` - [ ] **Step 2: Write the failing tests** Replace contents of `tests/Feature/Livewire/Fuel/RecommendationTest.php`: ```php assertStatus(200) ->assertSet('prediction', null) ->assertDontSee('Recommendation'); }); it('shows recommendation card when stations-found includes a prediction', function () { $prediction = [ 'action' => 'fill_now', 'confidence_score' => 80.0, 'confidence_label' => 'high', 'reasoning' => 'Prices are rising sharply.', 'predicted_direction' => 'up', 'predicted_change_pence' => 3.5, ]; Livewire::test(Recommendation::class) ->dispatch('stations-found', results: [], meta: [], prediction: $prediction, radius: 5) ->assertSet('prediction', $prediction) ->assertSee('Recommendation') ->assertSee('Fill up now'); }); it('shows nothing when stations-found has null prediction', function () { Livewire::test(Recommendation::class) ->dispatch('stations-found', results: [], meta: [], prediction: null, radius: 5) ->assertSet('prediction', null) ->assertDontSee('Recommendation'); }); it('clears previous prediction when new stations-found fires with null prediction', function () { $prediction = [ 'action' => 'fill_now', 'confidence_score' => 80.0, 'confidence_label' => 'high', 'reasoning' => 'Prices rising.', 'predicted_direction' => 'up', 'predicted_change_pence' => 3.5, ]; Livewire::test(Recommendation::class) ->dispatch('stations-found', results: [], meta: [], prediction: $prediction, radius: 5) ->assertSee('Recommendation') ->dispatch('stations-found', results: [], meta: [], prediction: null, radius: 5) ->assertDontSee('Recommendation'); }); ``` - [ ] **Step 3: Run tests to confirm they fail** ```bash php artisan test --compact --filter="RecommendationTest" --timeout=10 ``` Expected: all fail — class not found or no `handle` method. - [ ] **Step 4: Implement Recommendation.php** Replace contents of `app/Livewire/Public/Fuel/Recommendation.php`: ```php prediction = $prediction; } public function render(): View { return view('livewire.public.fuel.recommendation'); } } ``` - [ ] **Step 5: Implement recommendation.blade.php** Replace contents of `resources/views/livewire/public/fuel/recommendation.blade.php`: ```blade
@if ($prediction)
@endif
``` - [ ] **Step 6: Run tests to confirm they pass** ```bash php artisan test --compact --filter="RecommendationTest" --timeout=10 ``` Expected: all 4 tests pass. - [ ] **Step 7: Run Pint** ```bash vendor/bin/pint app/Livewire/Public/Fuel/Recommendation.php --format agent ``` - [ ] **Step 8: Commit** ```bash git add app/Livewire/Public/Fuel/Recommendation.php resources/views/livewire/public/fuel/recommendation.blade.php tests/Feature/Livewire/Fuel/RecommendationTest.php git commit -m "feat: extract fuel.recommendation Livewire component" ``` --- ## Task 4: fuel.map component + JS update **Files:** - Create: `app/Livewire/Public/Fuel/Map.php` - Create: `resources/views/livewire/public/fuel/map.blade.php` - Create: `tests/Feature/Livewire/Fuel/MapTest.php` - Modify: `resources/js/maps/station-map.js` - Modify: `resources/views/components/fuel/station-map.blade.php` - [ ] **Step 1: Scaffold** ```bash php artisan make:livewire Public/Fuel/Map --no-interaction php artisan make:test Feature/Livewire/Fuel/MapTest --pest --no-interaction ``` - [ ] **Step 2: Write the failing tests** Replace contents of `tests/Feature/Livewire/Fuel/MapTest.php`: ```php assertStatus(200); }); it('dispatches map-update browser event when stations-found is received', function () { Livewire::test(Map::class) ->dispatch('stations-found', results: [['name' => 'BP Garage']], meta: ['lat' => 51.5, 'lng' => -0.1, 'count' => 1], radius: 5, prediction: null ) ->assertDispatched('map-update'); }); it('passes radius in map-update payload', function () { Livewire::test(Map::class) ->dispatch('stations-found', results: [], meta: ['lat' => 51.5, 'lng' => -0.1, 'count' => 0], radius: 10, prediction: null ) ->assertDispatched('map-update', fn ($event, $params) => $params['radius'] === 10 ); }); ``` - [ ] **Step 3: Run tests to confirm they fail** ```bash php artisan test --compact --filter="MapTest" --timeout=10 ``` Expected: all fail — class not found. - [ ] **Step 4: Implement Map.php** Replace contents of `app/Livewire/Public/Fuel/Map.php`: ```php dispatch('map-update', results: $results, meta: $meta, radius: $radius); } public function render(): View { return view('livewire.public.fuel.map'); } } ``` - [ ] **Step 5: Implement map.blade.php** Replace contents of `resources/views/livewire/public/fuel/map.blade.php`: ```blade
``` - [ ] **Step 6: Run PHP tests to confirm they pass** ```bash php artisan test --compact --filter="MapTest" --timeout=10 ``` Expected: all 3 tests pass. - [ ] **Step 7: Update station-map.js** Replace the `init()` method in `resources/js/maps/station-map.js` (lines 59–74). The old `init()`: ```js init() { injectUserMarkerStyles(); this._map = L.map(this.$el, { zoomControl: true }).setView(UK_CENTRE, UK_ZOOM); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19, }).addTo(this._map); if (this.results && this.results.length > 0) { this._plotMarkers(); } this.$watch('results', () => this._plotMarkers()); this.locateUser(); }, ``` New `init()`: ```js init() { injectUserMarkerStyles(); this._map = L.map(this.$el, { zoomControl: true }).setView(UK_CENTRE, UK_ZOOM); L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { attribution: '© OpenStreetMap contributors', maxZoom: 19, }).addTo(this._map); window.addEventListener('map-update', (e) => { this.results = e.detail.results; this.meta = e.detail.meta; this.radius = e.detail.radius; this._plotMarkers(); }); this.locateUser(); }, ``` - [ ] **Step 8: Update station-map.blade.php** Replace the full contents of `resources/views/components/fuel/station-map.blade.php`: ```blade
``` - [ ] **Step 9: Run Pint** ```bash vendor/bin/pint app/Livewire/Public/Fuel/Map.php --format agent ``` - [ ] **Step 10: Commit** ```bash git add app/Livewire/Public/Fuel/Map.php resources/views/livewire/public/fuel/map.blade.php tests/Feature/Livewire/Fuel/MapTest.php resources/js/maps/station-map.js resources/views/components/fuel/station-map.blade.php git commit -m "feat: extract fuel.map component and wire Leaflet to map-update browser event" ``` --- ## Task 5: Strip FuelFinder to shell **Files:** - Modify: `app/Livewire/Public/FuelFinder.php` - Modify: `resources/views/livewire/public/fuel-finder.blade.php` - Modify: `tests/Feature/Livewire/FuelFinderTest.php` - [ ] **Step 1: Update FuelFinder.php** Replace the full contents of `app/Livewire/Public/FuelFinder.php`: ```php {{-- HEADER --}}
FuelAlert
@auth @else @endauth
{{-- MAIN --}}
{{-- BOTTOM TAB BAR --}} @php $tabs = [ ['label' => 'Prices', 'icon' => 'lucide:fuel', 'route' => 'home'], ['label' => 'Alerts', 'icon' => 'lucide:bell', 'route' => null], ['label' => 'Forecourts', 'icon' => 'lucide:map-pin', 'route' => null], ['label' => 'Trends', 'icon' => 'lucide:trending-up', 'route' => null], ]; @endphp ``` - [ ] **Step 3: Update FuelFinderTest.php** Replace the full contents of `tests/Feature/Livewire/FuelFinderTest.php`: ```php assertStatus(200); }); ``` - [ ] **Step 4: Run the full test suite** ```bash php artisan test --compact --timeout=10 ``` Expected: 1 failure in `StatsOverviewWidgetTest` (pre-existing, unrelated to this work). All other tests pass, including the new `SearchTest`, `StationListTest`, `RecommendationTest`, and `MapTest`. - [ ] **Step 5: Run Pint** ```bash vendor/bin/pint app/Livewire/Public/FuelFinder.php --format agent ``` - [ ] **Step 6: Commit** ```bash git add app/Livewire/Public/FuelFinder.php resources/views/livewire/public/fuel-finder.blade.php tests/Feature/Livewire/FuelFinderTest.php git commit -m "feat: strip FuelFinder to layout shell, wire sub-components" ```