Skip to content

fix: SwipeDeck registry lifecycle for dynamic ids - #26

Merged
saseungmin merged 6 commits into
mainfrom
fix/swipe-deck-registry-lifecycle
Aug 12, 2026
Merged

fix: SwipeDeck registry lifecycle for dynamic ids#26
saseungmin merged 6 commits into
mainfrom
fix/swipe-deck-registry-lifecycle

Conversation

@saseungmin

@saseungmin saseungmin commented Aug 11, 2026

Copy link
Copy Markdown
Member

Summary

  • retain registry stores while committed SwipeDeck.Root or public hook consumers use an id
  • evict zero-consumer entries after a guarded microtask
  • preserve StrictMode replay, late commits, providerless id lookup, and duplicate-Root rejection
  • document stable route ids and add English/Korean lifecycle guidance
  • add a patch Changeset and registry/React regression coverage

Why

Apps can mount multiple screens from one createSwipeDeck() factory by assigning each screen a stable route-scoped id. The registry previously had no deletion path, so every dynamic id kept its store, actions, events, and interaction values for the factory lifetime after the screen unmounted.

Root cause

getStore(id) inserted stores into a factory-owned Map, but Roots and public hooks did not report committed ownership or cleanup. The registry therefore could not distinguish an active id from one whose final consumer had unmounted.

Behavior

The duplicate-Root invariant is unchanged: two simultaneous Roots from the same factory still require distinct ids. Store identity remains stable while at least one committed Root or hook retains the id. After the final release and guarded deferred eviction, a later consumer receives a fresh store.

A fresh id read by a render that never commits remains a documented best-effort cleanup limitation. Eagerly evicting that entry could race a delayed concurrent commit.

Validation

  • pnpm test --runInBand --watchman=false — 15 suites, 213 tests passed
  • targeted registry/integration suites — 2 suites, 57 tests passed
  • pnpm lint
  • pnpm typecheck
  • pnpm format:check
  • pnpm clean && pnpm build
  • pnpm docs:typecheck
  • pnpm docs:build
  • verified English/Korean info callouts and non-empty generated SSG-MD output

Dynamic route ids need a deletion path.

Factory lookup stays id-based, so the registry now tracks committed consumers.

Final eviction is deferred to avoid StrictMode replay and late commit races.

Constraint: Factory hooks resolve stores by id without Provider or Scope ancestry

Rejected: Evict stores from getStore; render allocation cannot prove commit

Confidence: high

Scope-risk: moderate

Directive: Keep render allocation separate from committed-consumer eviction

Tested: Registry and integration lifecycle suites before commit

Not-tested: Memory profiling of never-committed abandoned renders
Roots and public hooks are the committed registry consumers.

Retain the store in layout effects and release it during cleanup.

Root attach errors release the held store before rethrowing.

Constraint: Duplicate Root protection must remain unchanged for a factory id

Rejected: Allow two Roots for one id; external hook ownership would be ambiguous

Confidence: high

Scope-risk: moderate

Directive: Keep attach cleanup ordering before changing duplicate Root behavior

Tested: Integration lifecycle suites before commit

Not-tested: Native-device navigation memory profile
Dynamic route ids now follow committed consumer lifetime.
Cover eviction, late retain, StrictMode, and duplicate Root cleanup.
Document stable ids and the abandoned-render limitation in both languages.

Constraint: Duplicate Roots for one factory id must remain rejected
Rejected: Treat unique ids as item keys | deck ids identify factory-scoped stores
Confidence: high
Scope-risk: moderate
Directive: Update lifecycle tests and docs together when changing registry ownership
Tested: 15 Jest suites / 213 tests; lint; typecheck; format; builds; docs checks
Not-tested: Memory profiling of fresh ids from renders that never commit
Stable route ids avoid concurrent Root collisions but need lifecycle cleanup.
Explain the committed-consumer contract, duplicate-Root rule, and residual risk.
Ship the behavior as a minor Changeset for downstream consumers.

Constraint: Deck ids identify factory-scoped stores rather than card items
Rejected: Hide the render-abort limitation | eager cleanup can race delayed commits
Confidence: high
Scope-risk: narrow
Directive: Keep English, Korean, API, and multi-instance guidance aligned
Tested: Docs typecheck/build and generated callout output
Not-tested: Memory profiling of fresh ids from renders that never commit
@changeset-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 47d4ac8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@react-native-motion-kit/swipe-deck Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying react-native-swipe-deck with  Cloudflare Pages  Cloudflare Pages

Latest commit: b3ab7ef
Status: ✅  Deploy successful!
Preview URL: https://59ab23a5.react-native-swipe-deck.pages.dev
Branch Preview URL: https://fix-swipe-deck-registry-life.react-native-swipe-deck.pages.dev

View logs

@saseungmin
saseungmin marked this pull request as ready for review August 11, 2026 11:49
@saseungmin saseungmin changed the title Fix SwipeDeck registry lifecycle for dynamic ids fix: SwipeDeck registry lifecycle for dynamic ids Aug 11, 2026
The change repairs internal registry retention without adding public API.
Align the Changeset with this repository's patch convention for bug fixes.

Constraint: Identity resets only after the final committed consumer releases the id
Rejected: Minor bump | no new public API or additive user-facing surface
Confidence: high
Scope-risk: narrow
Tested: Changeset status, Oxfmt check, and git diff check

@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: b3ab7efe07

ℹ️ 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 src/registry/registry.ts
Observe canonical store identity before committed hooks and Roots retain it.

This rebases interrupted renders when another store has claimed the same id.

The mismatch guard remains an internal invariant.

Constraint: Providerless factory hooks resolve decks globally by id
Rejected: Ignore stale retain conflicts | consumers would read an orphaned store
Rejected: Per-id numeric generations | adds tombstones without better identity checks
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: Keep identity snapshots pure and notify subscribers outside render
Tested: Jest 16 suites / 216 tests; typecheck; lint; format; library and docs builds
Not-tested: React 18 runtime matrix
Not-tested: Fresh-id aborted renders retain the documented cleanup limitation
@saseungmin

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 47d4ac85e4

ℹ️ 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".

@saseungmin
saseungmin merged commit 85ccb41 into main Aug 12, 2026
3 checks passed
@saseungmin
saseungmin deleted the fix/swipe-deck-registry-lifecycle branch August 12, 2026 01:22
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