fix(desktop): track isDisabled flips in the adapter no-op notify gate

Review follow-up: __internal_setAdapter assigned this.isDisabled before
the fast path but never fed it into the new 'changed' flag, so an
isDisabled-only flip on an otherwise-identical adapter swap would have
been silently swallowed. Seed 'changed' with the isDisabled comparison
and add a guard test (mutation-checked: reverting the seed fails it).
This commit is contained in:
kshitij
2026-08-24 00:00:02 +05:30
parent 4019518edd
commit 90803f22a5
2 changed files with 27 additions and 2 deletions
@@ -146,7 +146,9 @@ class IncrementalExternalStoreThreadRuntimeCore extends ExternalStoreThreadRunti
}
const isRunning = store.isRunning ?? false
this.isDisabled = store.isDisabled ?? false
const newDisabled = store.isDisabled ?? false
const disabledChanged = this.isDisabled !== newDisabled
this.isDisabled = newDisabled
const oldStore = self._store
self._store = store
@@ -158,7 +160,7 @@ class IncrementalExternalStoreThreadRuntimeCore extends ExternalStoreThreadRunti
// (render -> new literal -> setAdapter -> notify -> render), which React
// kills with "Maximum update depth exceeded" and takes the session tile
// down with its error boundary.
let changed = false
let changed = disabledChanged
if (this.extras !== store.extras) {
this.extras = store.extras
@@ -115,6 +115,29 @@ describe('IncrementalExternalStoreThreadRuntimeCore adapter swap notifications',
expect(notifications).toBeGreaterThan(0)
})
it('still notifies on an isDisabled flip even when transcript and run state are unchanged', () => {
const repo = repositoryOf([message('a', 'one')])
const core = new IncrementalExternalStoreRuntimeCore(adapterWith(repo))
const thread = core.threads.getMainThreadRuntimeCore()
let notifications = 0
thread.subscribe(() => {
notifications += 1
})
core.setAdapter(adapterWith(repo, { isDisabled: true }))
expect(notifications).toBeGreaterThan(0)
// And flipping back also notifies — but an unchanged repeat stays silent.
core.setAdapter(adapterWith(repo, { isDisabled: false }))
const afterFlipBack = notifications
core.setAdapter(adapterWith(repo, { isDisabled: false }))
expect(notifications).toBe(afterFlipBack)
})
it('a subscriber that swaps a fresh-but-identical adapter on every notify must not recurse unboundedly', () => {
// Direct simulation of the feedback loop: the subscriber plays the role of
// React re-rendering ChatRuntimeBoundary (new literal -> setAdapter). With