Hook system hardening: enforce invariants and expand hook edge-case coverage#80
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d2f76c6f85
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!state.needsRender) { | ||
| const evalRes = evaluateAppStateSelections( | ||
| registry.getAppStateSelections(instanceId), | ||
| compositeRuntime.appState, | ||
| ); |
There was a problem hiding this comment.
Defer selector snapshot checks until skip preconditions hold
The useAppState snapshot evaluation runs whenever !state.needsRender, before we know whether this instance is actually eligible for render-skipping, so selectors from the previous render are invoked even when props have changed and a normal re-render is required. If a selector closure depends on old props/state shape (for example, it reads a branch that is only valid for the previous props), this path returns ZRUI_USER_CODE_THROW and aborts commit instead of rendering with the new props and new selectors. Move snapshot evaluation behind the skip gate conditions (at least after confirming props/widget identity still match) to avoid stale-selector crashes on prop transitions.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in a9750bd. Selector snapshot evaluation now runs only after full skip preconditions are met (stable local state, matching widget identity, matching props, and existing previous child), so prop transitions no longer execute stale selector closures before rerender. Added regression test prop changes do not evaluate stale selectors before rerender in hooks.useAppState.test.ts.
Summary
This hardens composite hook runtime behavior and adds exhaustive deterministic regression coverage for hook edge cases.
Runtime hardening
useStatesetters are no-ops by invalidating generation on delete paths.useAppStateselector snapshot tracking and commit-time rerender gating:Object.is-equal.Tests added (82 new)
hooks.useState.test.ts(24)hooks.useEffect.test.ts(24)hooks.useRef.test.ts(15)hooks.useAppState.test.ts(12)hooks.ordering.test.ts(7)Coverage includes batching/functional updaters, post-unmount safety, deps and cleanup chains, reverse cleanup order on unmount, async cleanup invocation, selector gating and referential equality behavior, multi-instance isolation, and hook ordering/count invariants.
Docs
useAppStateselector gating, and hook-count consistency.Validation
npm run buildnpm run typechecknode scripts/run-tests.mjsnpm run lintAll commands passed in this worktree.