Skip to content

Seed test identities once and keep the identity list unique - #244

Merged
soamdesai-tfh merged 1 commit into
mainfrom
soam/dedupe-test-identities
Sep 15, 2026
Merged

soamdesai-tfh merged 1 commit into
mainfrom
soam/dedupe-test-identities

Conversation

@soamdesai-tfh

Copy link
Copy Markdown
Contributor

Summary

Fixes the identity picker showing every test identity twice (then three times, and so on) after reloads.

Root cause. zustand's useStore passes api.getInitialState to React as the server snapshot. While the SSR'd page hydrates, useSyncExternalStore renders with that pre-hydration snapshot, identities: [], even though persist has already hydrated synchronously from localStorage. The seeding effect on / ran on that stale closure and inserted five more identities on every full load. Ids are deterministic (ZkIdentity(String(idNumber))), so the duplicates carried identical ids.

Fix.

  • insertIdentity is a no-op when the id already exists.
  • The persist merge dedupes persisted identities by id (first occurrence wins) and falls back to the current list when the persisted field is not an array. Profiles that already contain duplicates heal on their next load.
  • / seeds from useIdentityStore.getState() once persist.hasHydrated() is true, otherwise via onFinishHydration, instead of reading the hook's identities.

Verification

  • New tests/identity-store.test.ts installs an in-memory localStorage, imports the real store, and covers hydration dedupe, idempotent insert (including persisted output) and a malformed identities field. pnpm test: 28/28.
  • pnpm typecheck, pnpm lint, pnpm run spellcheck clean.
  • Headless Chrome against next dev: a profile holding 15 duplicated rows healed to 5 on reload; a cold profile seeded exactly 5 and stayed at 5 across three more loads; /select-id renders one row per id.

Not in this PR

The same stale snapshot makes the redirect effect on / push /select-id before /id/<active> on every full load. Harmless, but worth a follow-up.

🤖 Generated with Claude Code

Every full page load of `/` inserted five more identities. zustand's
`useStore` hands React `getInitialState` as the server snapshot, so while
the SSR'd page hydrates, `useSyncExternalStore` renders with the
pre-hydration state (`identities: []`) even though `persist` has already
read localStorage. The seeding effect ran on that stale closure and, since
identity ids are derived deterministically from the identity number, wrote
exact duplicates: 5 rows became 10, then 15.

- `insertIdentity` is idempotent by id.
- The persist `merge` dedupes persisted identities by id and tolerates a
  malformed list, which also heals profiles that already hold duplicates.
- `/` seeds from the live store state once `persist.hasHydrated()` is true
  (or on `onFinishHydration`), instead of from the hook's `identities`.
- Tests cover hydration dedupe, idempotent insert and malformed storage.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 15, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
worldcoin-simulator Ready Ready Preview Sep 15, 2026 12:25am UTC

Request Review

@soamdesai-tfh
soamdesai-tfh merged commit 6247938 into main Sep 15, 2026
10 checks passed
@soamdesai-tfh
soamdesai-tfh deleted the soam/dedupe-test-identities branch September 15, 2026 00:31

This branch was successfully deployed

1 active deployment
Preview — f18ceb4e Deployed Sep 15, 2026 by vercel[bot]
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