fix(ui): set active server at mount only (race-free)

Revert the hydrate-on-every-request sync — writing the single global active-server
session on every async request (slow load(), polls, parallel tabs) is inherently
last-writer-wins and races. Setting it only at mount (once per deliberate
navigation) is race-free and fixes the visible drift. bfcache restore + multi-tab
remain inherent edges of the single-session model — documented in the handoff.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/v1-foundation
boban 2026-06-14 19:43:24 +02:00
parent 7ea789c078
commit 0b1b5357d2
2 changed files with 6 additions and 34 deletions

View File

@ -61,29 +61,17 @@ class Show extends Component
*/
public function mount(): void
{
$this->syncActiveServer();
// Viewing a server's details makes it the ACTIVE server, so the sidebar switcher
// (and the scoped sections) reflect the server on screen. Set ONLY at mount — i.e.
// once per deliberate navigation — so a slow in-flight load() or a parallel tab
// can never write a stale value over a newer selection (the single-session active
// server is last-writer-wins; we keep writes to deliberate navigations only).
session(['active_server_id' => $this->server->id]);
// identity + gauges render from the persisted row immediately;
// the live snapshot loads lazily via wire:init -> load().
}
/**
* Re-assert this server as the ACTIVE one on EVERY request to this component (mount,
* the wire:poll tick, any action). This is what keeps the sidebar switcher + scoped
* sections in sync with the server on screen and it also covers a wire:navigate
* bfcache restore (Back/Forward), which skips mount() but still hits the server on
* the next poll/interaction. Server-side + idempotent, so there is no request race.
*/
public function hydrate(): void
{
$this->syncActiveServer();
}
private function syncActiveServer(): void
{
session(['active_server_id' => $this->server->id]);
}
/** Lazy: pull the full SSH snapshot after the shell renders, behind skeletons. */
public function load(FleetService $fleet): void
{

View File

@ -29,22 +29,6 @@ class ServerContextSyncTest extends TestCase
$this->assertSame($b->id, session('active_server_id'));
}
public function test_interacting_with_a_details_page_resyncs_active_server(): void
{
// Simulates a bfcache restore where mount() did not run and the session points
// elsewhere: the next request to the component (poll/action → hydrate) re-syncs.
$this->actingAs(User::factory()->create());
$a = $this->server('A');
$b = $this->server('B');
$component = Livewire::test(Show::class, ['server' => $a]);
session(['active_server_id' => $b->id]); // drift (as after Back/Forward)
$component->call('pollMetrics');
$this->assertSame($a->id, session('active_server_id'));
}
public function test_switching_on_a_details_page_targets_the_new_server(): void
{
$a = $this->server('A');