fix(app-state): actually send the coalesced broadcast - #82
Merged
Conversation
notifyRenderer's timer callback nulled broadcastTimer and then called flushRenderer, which returns early when broadcastTimer is null. Since flushRenderer holds the only webContents.send for app:state-updated, and nothing outside the tests calls it, the renderer never received a single state push. The renderer does not recover on its own: use-app-state only starts its polling fallback when onAppStateUpdated is absent, so it subscribed to a channel that never fired and froze on the snapshot it took at mount. Start then re-toasted the missing-configuration error forever because interviewConfigLoaded never arrived, and a fresh login never left the form because AuthLayout waits on isLoggedIn from the broadcast. Split the send out into sendToRenderer so the pending check stays on flushRenderer, where it is the documented contract, and off the timer path, where it was self-cancelling. Every existing check flushed synchronously while the timer was still pending - the one arrangement in which the send worked - so add a check that lets the timer fire on its own. Closes #81 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #81.
The main process never delivered a single
app:state-updatedpush to the renderer, so the renderer ran forever on the one snapshot it took when the first component mounted. Reported as "the Start button does nothing"; a fresh login was equally dead.Root cause
flushRenderer()opens withif (!this.broadcastTimer) return;and holds the onlywebContents.send('app:state-updated', ...)insrc/main/. Nothing outsidetest/calls it. So every real broadcast went through the timer path, and the timer path sent nothing.The renderer never recovers because
use-app-state.tsxstarts its polling fallback only whenonAppStateUpdatedis absent. It is present, so the renderer subscribed to a channel that never fired and never polled.Change
Extract the send into
private sendToRenderer(). The timer callback calls it directly;flushRenderer()keeps its pending check - which is its documented contract, and what theidentical updates do not broadcastcheck relies on - then calls it. No signature or semantic change toflushRenderer().Test
Every existing check in
test/app-state.test.mjscallsflushRenderer()synchronously while the timer is still pending. That is the one arrangement in which the send worked, which is exactly why this shipped green. Added a check that lets the timer fire on its own with no flush at all.Verified red before the fix and green after (rebuilding
electron-dist/in between -node test/run.mjsalone runs stale output):pnpm test:main,pnpm lint, andpnpm buildall pass on the branch.What this unblocks
Every consumer of the push channel, not just Start: fresh login navigation, the
isLoggedIn: nullstartup window that could pin MainPage on "Authenticating…" forever, Idle -> Starting -> Running button transitions andTransitionOverlay, live transcripts and both suggestion panels (stuck on placeholder content for a whole interview), credits / user role / backend-live indicators, and the Clear reset from 3a7b660 whose broadcast could not send.Also audited
Same class of defect across the client, all clear: every
ipcMainchannel has a matching preload binding and vice versa; all push channels main sends have live preload listeners; the timers inaction-lock.service.tsand the stall timers in both suggestion services are correct - none repeats the clear-then-call-a-guarded-method shape.Two pre-existing items noted in #81 as out of scope: the
audioInputDeviceNotFounduse-before-declaration incontrol-panel/index.tsx, andenumerateDevices()returning blank labels before mic permission is granted.🤖 Generated with Claude Code