perf(store): keep runtimeEnvironments identity across no-op catalog lists

A no-op list or hydrate always allocated a new catalog because redact remaps
endpoints[] and IPC structuredClone rebuilds the rest. Object.is selectors
then missed on every 60s TTL refresh. Reconcile rows with areValuesEqual and
return the previous state when the catalog, statuses, and removed-id set are
unchanged after hydration.

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Neil
2026-08-12 22:28:31 -07:00
co-authored by Orca
parent 889dd4c7d2
commit b6e53ccfeb
3 changed files with 199 additions and 2 deletions
@@ -0,0 +1,179 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import {
redactRuntimeEnvironment,
type KnownRuntimeEnvironment
} from '../../../../shared/runtime-environments'
import type { RuntimeStatus } from '../../../../shared/runtime-types'
import {
clearRuntimeEnvironmentConnectionGenerationsForTests,
getRuntimeEnvironmentConnectionGeneration
} from './runtime-status'
import { createTestStore } from './store-test-helpers'
vi.mock('sonner', () => ({
toast: { warning: vi.fn(), dismiss: vi.fn() }
}))
// Why: production rows carry nested endpoints. A scalar-only fixture reconciles even
// when the walker is broken, which is how a redact-only or Object.is fix stays green.
const pairingRevision = 1_700_000_000_000
const knownEndpoint: KnownRuntimeEnvironment['endpoints'][number] = {
id: 'ws-a',
kind: 'websocket',
label: 'WebSocket',
endpoint: 'wss://box.example:8787',
deviceToken: 'device-token-a',
publicKeyB64: 'public-key-a'
}
const knownEnvironment: KnownRuntimeEnvironment = {
id: 'env-a',
name: 'Dev Box',
createdAt: pairingRevision,
updatedAt: pairingRevision,
pairingRevision,
lastUsedAt: 1_700_000_100_000,
runtimeId: 'runtime-a',
source: 'manual',
endpoints: [knownEndpoint],
preferredEndpointId: 'ws-a'
}
function cloneRedactedCatalog(
environment: KnownRuntimeEnvironment = knownEnvironment
): ReturnType<typeof redactRuntimeEnvironment>[] {
return structuredClone([redactRuntimeEnvironment(environment)])
}
function makeStatus(): RuntimeStatus {
return {
runtimeId: 'rt',
rendererGraphEpoch: 0,
graphStatus: 'ready',
authoritativeWindowId: null,
liveTabCount: 0,
liveLeafCount: 0,
runtimeProtocolVersion: 3,
minCompatibleRuntimeClientVersion: 3
}
}
beforeEach(() => {
clearRuntimeEnvironmentConnectionGenerationsForTests()
vi.stubGlobal('window', { api: {}, dispatchEvent: vi.fn() })
})
afterEach(() => {
vi.unstubAllGlobals()
})
describe('setRuntimeEnvironments catalog identity', () => {
it('keeps the array, row, and endpoints across independently cloned no-op lists', () => {
const store = createTestStore()
store.getState().setRuntimeEnvironments(cloneRedactedCatalog())
const previous = store.getState().runtimeEnvironments
const second = cloneRedactedCatalog()
expect(second).not.toBe(previous)
expect(second[0]).not.toBe(previous[0])
expect(second[0]?.endpoints).not.toBe(previous[0]?.endpoints)
store.getState().setRuntimeEnvironments(second)
const next = store.getState().runtimeEnvironments
expect(next).toBe(previous)
expect(next[0]).toBe(previous[0])
expect(next[0]?.endpoints).toBe(previous[0]?.endpoints)
})
it('keeps the same empty array across two empty lists', () => {
const store = createTestStore()
store.getState().setRuntimeEnvironments([])
const empty = store.getState().runtimeEnvironments
store.getState().setRuntimeEnvironments([])
expect(store.getState().runtimeEnvironments).toBe(empty)
expect(empty).toEqual([])
})
it('flips catalog hydrated and settled on the first write', () => {
const store = createTestStore()
expect(store.getState().runtimeEnvironmentCatalogHydrated).toBe(false)
expect(store.getState().runtimeEnvironmentCatalogSettled).toBe(false)
store.getState().setRuntimeEnvironments(cloneRedactedCatalog())
expect(store.getState().runtimeEnvironmentCatalogHydrated).toBe(true)
expect(store.getState().runtimeEnvironmentCatalogSettled).toBe(true)
})
it('allocates a new row when a public name changes', () => {
const store = createTestStore()
store.getState().setRuntimeEnvironments(cloneRedactedCatalog())
const previous = store.getState().runtimeEnvironments
store
.getState()
.setRuntimeEnvironments(cloneRedactedCatalog({ ...knownEnvironment, name: 'Lab' }))
const next = store.getState().runtimeEnvironments
expect(next).not.toBe(previous)
expect(next[0]).not.toBe(previous[0])
expect(next[0]?.name).toBe('Lab')
})
it('allocates a new row when an endpoint URL changes', () => {
const store = createTestStore()
store.getState().setRuntimeEnvironments(cloneRedactedCatalog())
const previous = store.getState().runtimeEnvironments
store.getState().setRuntimeEnvironments(
cloneRedactedCatalog({
...knownEnvironment,
endpoints: [{ ...knownEndpoint, endpoint: 'wss://box.example:9999' }]
})
)
const next = store.getState().runtimeEnvironments
expect(next).not.toBe(previous)
expect(next[0]).not.toBe(previous[0])
expect(next[0]?.endpoints).not.toBe(previous[0]?.endpoints)
expect(next[0]?.endpoints[0]?.endpoint).toBe('wss://box.example:9999')
})
it('allocates a new row when lastUsedAt or updatedAt changes', () => {
const store = createTestStore()
store.getState().setRuntimeEnvironments(cloneRedactedCatalog())
const previous = store.getState().runtimeEnvironments
store.getState().setRuntimeEnvironments(
cloneRedactedCatalog({
...knownEnvironment,
lastUsedAt: 1_700_000_200_000,
updatedAt: 1_700_000_200_000
})
)
const next = store.getState().runtimeEnvironments
expect(next).not.toBe(previous)
expect(next[0]).not.toBe(previous[0])
expect(next[0]?.lastUsedAt).toBe(1_700_000_200_000)
expect(next[0]?.updatedAt).toBe(1_700_000_200_000)
})
it('still drops status and advances generation on a pairingRevision change', () => {
const store = createTestStore()
store.getState().setRuntimeEnvironments(cloneRedactedCatalog())
store.getState().setRuntimeEnvironmentStatus('env-a', { status: makeStatus(), checkedAt: 1 })
const before = getRuntimeEnvironmentConnectionGeneration('env-a')
store
.getState()
.setRuntimeEnvironments(
cloneRedactedCatalog({ ...knownEnvironment, pairingRevision: pairingRevision + 1 })
)
expect(store.getState().runtimeStatusByEnvironmentId.has('env-a')).toBe(false)
expect(getRuntimeEnvironmentConnectionGeneration('env-a')).toBe(before + 1)
expect(store.getState().runtimeEnvironments[0]?.pairingRevision).toBe(pairingRevision + 1)
})
})
@@ -799,7 +799,7 @@ describe('runtime-status slice', () => {
expect(list).toHaveBeenCalledTimes(2)
expect(getStatus).toHaveBeenCalledTimes(2)
expect(publications).toBe(4)
expect(publications).toBe(3)
expect(store.getState().runtimeStatusByEnvironmentId.get('env-a')?.status?.runtimeId).toBe(
'runtime-2'
)
@@ -13,6 +13,7 @@ import {
dismissRuntimeDisconnectedToast,
showRuntimeDisconnectedToast
} from './runtime-environment-disconnect-toast'
import { reconcileCatalogRows } from './repo-identity-reconcile'
import { createRuntimeStatusHydration } from './runtime-status-hydration'
import { refreshRuntimeEnvironmentStatus } from './runtime-status-refresh'
@@ -151,8 +152,25 @@ export const createRuntimeStatusSlice: StateCreator<AppState, [], [], RuntimeSta
removedChanged = true
}
}
// Why: list()/hydrate always allocate (IPC structuredClone + redact remaps
// endpoints[]). Reuse equal rows so Object.is subscribers don't miss 100%.
const reconciled = reconcileCatalogRows(
s.runtimeEnvironments,
environments,
(environment) => environment.id
)
const catalogUnchanged = reconciled === s.runtimeEnvironments
if (
catalogUnchanged &&
s.runtimeEnvironmentCatalogHydrated &&
s.runtimeEnvironmentCatalogSettled &&
!statusesChanged &&
!removedChanged
) {
return s
}
return {
runtimeEnvironments: environments,
runtimeEnvironments: catalogUnchanged ? s.runtimeEnvironments : reconciled.slice(),
runtimeEnvironmentCatalogHydrated: true,
runtimeEnvironmentCatalogSettled: true,
...(statusesChanged ? { runtimeStatusByEnvironmentId: nextStatuses } : {}),