* fix: resolve keyboard freeze via sync render path and stable useAppState selectors
Two compounding React 19 defects caused keyboard input to freeze after
MCP notifications or rapid state updates:
Defect 2 (ink.tsx): The render() path used async updateContainer, which
batches updates across scheduler ticks. Keyboard events dispatched mid-render
drained faster than React processed them, causing input to appear frozen.
Fixed by switching to updateContainerSync + flushSyncWork (same pattern
already used in the unmount path).
Defect 4 (AppState.tsx): useAppState and useAppStateMaybeOutsideOfProvider
used React Compiler _c cache invalidation tied to selector identity. Inline
arrow selectors (new reference each render) invalidated the cache every cycle,
producing a new `get` function. useSyncExternalStore treats a new `get` as a
tearing signal, re-syncing state and re-rendering — causing a loop that
starved the input handler. Fixed with useRef + useCallback(fn, []) to give
useSyncExternalStore a permanently stable snapshot reference.
Note: AppState.tsx is React Compiler output. The _c bypass for these two
hooks is intentional — compiler cache invalidation on inline selectors is
the root cause of the tearing loop.
All 200 tests pass. Build and smoke test verified.
Closes#77, #220, #228
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: update selector refs during render instead of useLayoutEffect
Addresses review feedback on PR #266. The previous useLayoutEffect approach
updated selectorRef.current after the render phase, meaning a changed selector
(e.g. s => s.tasks[attachment.taskId] when taskId changes) would still read
stale data during the render it changed in.
Fix: assign selectorRef.current and storeRef.current directly during render
before useSyncExternalStore calls get(). Ref mutation during render is safe
here — it's synchronous and happens before the snapshot is read. get()
identity stays stable via useCallback(fn, []) so useSyncExternalStore never
sees a new subscription function and won't trigger re-render loops.
This is the standard pattern used by zustand and jotai for selector stability.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>