From e897a4802f7c4f74c276097e26d22cb207f038a0 Mon Sep 17 00:00:00 2001 From: Vantz Stockwell Date: Thu, 11 Jun 2026 18:41:19 -0400 Subject: [PATCH] fix(server): apply lifecycle reply state optimistically (heartbeat lag) The agent reply is authoritative for the action just taken; the fleet DB only updates on the next heartbeat (~10s), so the immediate refetch read a stale state and reverted the UI (Start -> still Stopped). Now apply the reply's state/uptime directly to the instance. Co-Authored-By: Claude Fable 5 --- frontend/src/stores/instances.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/frontend/src/stores/instances.ts b/frontend/src/stores/instances.ts index f81680a..93aa2fc 100644 --- a/frontend/src/stores/instances.ts +++ b/frontend/src/stores/instances.ts @@ -68,8 +68,10 @@ export const useInstancesStore = defineStore('instances', () => { } /** - * Send a lifecycle command to the current instance. Returns the agent's - * reply (which carries the new state); refetches so the list reflects it. + * Send a lifecycle command to the current instance and apply the agent's + * reply state OPTIMISTICALLY. The reply is authoritative for the action just + * taken; the fleet DB only catches up on the next heartbeat (~10s), so an + * immediate refetch would read a stale state and clobber the result. * Throws on failure so the view can toast. */ async function lifecycle(action: LifecycleAction): Promise> { @@ -78,13 +80,26 @@ export const useInstancesStore = defineStore('instances', () => { acting.value = action try { const res = await api.post>(`/instances/${id}/lifecycle`, { action }) - await fetchInstances() + applyReplyState(id, res) return res } finally { acting.value = null } } + /** Update an instance's state/uptime from a lifecycle/status reply. */ + function applyReplyState(id: string, res: Record): void { + if ((res as { status?: string }).status !== 'success') return + const stateObj = (res as { state?: { state?: string } }).state + const newState = stateObj?.state + const inst = instances.value.find((i) => i.id === id) + if (inst && typeof newState === 'string') { + inst.state = newState + const up = (res as { uptime_seconds?: number }).uptime_seconds + inst.uptime_seconds = typeof up === 'number' ? up : newState === 'running' ? inst.uptime_seconds : 0 + } + } + async function rcon(command: string): Promise> { const id = currentId.value if (!id) throw new Error('No instance selected')