refactor(engine): let features register their own engine handlers - #29613
Draft
chrisnojima wants to merge 1 commit into
Draft
refactor(engine): let features register their own engine handlers#29613chrisnojima wants to merge 1 commit into
chrisnojima wants to merge 1 commit into
Conversation
This was referenced Sep 9, 2026
chrisnojima
added this pull request to stack #29617
September 9, 2026 19:40
Two mechanisms distributed the same action stream. The deep one - subscribeToEngineAction, type-indexed and HMR-safe - already had ~136 subscriptions. The shallow ones were three central switches: 41 arms in constants/init/shared.tsx reaching into 16 stores, 13 more split by platform in constants/init/index.tsx, and 9 inside the config store. That made constants/init a compile-time dependency of every store it dispatched into. Each feature now registers its own handlers at module init through the same listenersByType the notifier already walks. The three switches are gone; onEngineIncoming is notifyEngineActionListeners. Order was case-arm position, which said nothing. It is now an explicit priority on the registration: sharedFirst < shared < config < component subscriptions < platform, matching the order those four tiers ran in before. The one arm with a genuine internal dependency - the inbox conversation badge map has to be rebuilt before anything derives tab counts from it - registers at sharedFirst. Registrations are permanent: a sign-out reset drops what components subscribed, but nothing re-runs module init to put a registration back. The duplicate gregor nonNull filter (and its duplicate warning) that the shared switch performed alongside the identical one in useNotifState is dropped. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015rccpV5nLxxC5opF5xzrz7
chrisnojima
force-pushed
the
nojima/HOTPOT-arch-09-engine-handlers
branch
from
September 11, 2026 01:47
878c7e0 to
55bacb9
Compare
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.
Problem
Two parallel notification-distribution mechanisms over the same action stream.
The good one is already deep:
subscribeToEngineAction(type, fn)— type-indexed, HMR-safe, self-resetting, used at ~136 sites across ~27 modules.The other was 66 hand-written case arms across three files:
constants/init/shared.tsxgetState()reach-ins into 16 storesconstants/init/index.tsxstores/config.tsxThe switch is shallow — its interface is a discriminated union as wide as its implementation — and it inverted the dependency, making
constants/init/a compile-time dependency of every store.Change
Each owner registers its own handlers at module init, through the same
listenersByTypemapnotifyEngineActionListenersalready walks.onEngineIncomingbecomes one line.constants/init/shared.tsx: 498 → 320 lines.Verified mechanically that all 58 distinct action types are registered — none dropped, none added (case labels extracted from
git show master:and diffed against everyregisterEngineHandlersblock).Order dependences found — now explicit priorities
sharedFirst -400 < shared -300 < config -200 < default 0 < platform 100NotifyBadges.badgeState—syncInboxBadgeStatemust rebuild the inbox badge map before anything derives tab counts →sharedFirst.badgeStateandgregorUI.pushStateappear in both the shared and config switches, shared first.NotifySession.loggedIn/loggedOutappear in both config and the desktop platform switch; the platform arm readsuserSwitchingand callsgetEngine().reset()after config'ssetLoggedIn. Sharpest one.notifyUserBlocked/trackingChangedare in the shared switch andtracker/identify-session.tsx; switch first.Intra-arm order is preserved by keeping each arm a single handler. Audited and found not order-dependent:
gregorUI.pushState's block-buttons vs notifications pair (disjoint writes).HMR hazards fixed
stores/config.tsxregistered inside the zustand creator, whose store instancecreateZustanddiscards on HMR — a hot reload would unregister the working handlers and install ones writing into an orphan while the app rendered from the survivor. Moved to module scope; the only one of 13 sites that was inside a creator.nextSeqwas module-local whilelistenersByTypelives onglobalThisunder__DEV__, so a hot reload of that module alone restarted it at 0 and inverted registration order. Now onglobalThisbeside the map.Deliberate drop, stated not silent
The shared switch's
pushStatearm recomputed the same nonNull gregor filter and emitted the samelogger.warnthatuseNotifStatealready emits for the same action. One warning remains.Validation
lint:allclean —0 bailed out, 0 whole-props deps, tsc clean both projects.jest --runInBand— 231 suites / 2251 tests (baseline 230 / 2236).stores/tests/notifications.test.tsno longer pokesdispatch.onEngineIncomingImpl— an internal that existed only because the switch called it.