Skip to content

Hook system hardening: enforce invariants and expand hook edge-case coverage#80

Merged
RtlZeroMemory merged 5 commits into
mainfrom
hooks-hardening
Feb 18, 2026
Merged

Hook system hardening: enforce invariants and expand hook edge-case coverage#80
RtlZeroMemory merged 5 commits into
mainfrom
hooks-hardening

Conversation

@RtlZeroMemory

Copy link
Copy Markdown
Owner

Summary

This hardens composite hook runtime behavior and adds exhaustive deterministic regression coverage for hook edge cases.

Runtime hardening

  • Enforced hook count invariants across renders (clear errors for conditional/mismatched hook counts).
  • Preserved hook-kind ordering errors with explicit mismatch messages.
  • Ensured post-unmount/stale useState setters are no-ops by invalidating generation on delete paths.
  • Changed unmount effect cleanup traversal to reverse declaration order.
  • Added per-instance useAppState selector snapshot tracking and commit-time rerender gating:
    • skip composite rerender when local invalidation is absent,
    • widget props are shallow-equal,
    • and all selected app-state values are 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

  • Updated composition guide with clarified hook behavior for cleanup ordering, useAppState selector gating, and hook-count consistency.

Validation

  • npm run build
  • npm run typecheck
  • node scripts/run-tests.mjs
  • npm run lint

All commands passed in this worktree.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread packages/core/src/runtime/commit.ts Outdated
Comment on lines +658 to +662
if (!state.needsRender) {
const evalRes = evaluateAppStateSelections(
registry.getAppStateSelections(instanceId),
compositeRuntime.appState,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@RtlZeroMemory
RtlZeroMemory merged commit 6288f03 into main Feb 18, 2026
18 of 20 checks passed
@RtlZeroMemory
RtlZeroMemory deleted the hooks-hardening branch February 19, 2026 17:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant