c0fae47cc7af6408f94fb5fe32a45183a1108ee7
The microphone was the loud one: DictateDialog's teardown was guarded by `if (!showDictate)`, which can never be true, because a cleanup sees the props of the render that registered it and only the open render registers one. So it never ran, and the mic, the AudioContext and the rAF loop stayed alive for the life of the tab. useAudioRecording had no unmount cleanup at all — closing a Chat panel mid-recording did the same thing, with no way to switch the recording indicator back off. Both now release on unmount; the second is pinned by a test that records, unmounts, and asserts the track stopped. The rest is the same shape. Two sockets registered a listener once and held the first render's callback forever — usePipelineRunner's carried a captured streamingText, so a re-render mid-run would have folded every later event into a stale buffer. PanelSlot built its default header as a component *type* inside render, which React cannot match against the previous one. WorkspaceView handed every panel a fresh context object on every render, including each frame of a maximize animation. VideoPlayer's comment claimed a dependency list that the code did not have; the list is fine (sendReport never changes identity) and the comment now says why that has to stay true. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Description
No description provided
42 MiB
Languages
TypeScript
90.9%
Shell
4.7%
JavaScript
4.1%
CSS
0.2%
HTML
0.1%