Fix/eid wallet binding data loading#1088
Conversation
📝 WalkthroughWalkthroughThe eID wallet now propagates personal-binding query failures, memoizes vault URI resolution, uses shared typed GraphQL requests for binding documents, and retains social-binding rows across navigation. Vitest coverage and test environment placeholders were updated accordingly. ChangeseID wallet loading and caching
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@infrastructure/eid-wallet/src/routes/`(app)/social-bindings/+page.svelte:
- Around line 1-8: Update the module-level cache used by the social bindings
page, including cachedContacts and hasEverLoaded, so it is scoped to the
authenticated user via eName or cleared during sign-out. Ensure a new login
cannot render the previous user’s contacts before the current vault finishes
loading, while preserving the existing cache behavior for the same user.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 65838db3-0c72-4482-91b5-6ffec886f1e4
📒 Files selected for processing (8)
infrastructure/eid-wallet/package.jsoninfrastructure/eid-wallet/src/lib/utils/personalBinding.spec.tsinfrastructure/eid-wallet/src/lib/utils/personalBinding.tsinfrastructure/eid-wallet/src/lib/utils/socialBinding.spec.tsinfrastructure/eid-wallet/src/lib/utils/socialBinding.tsinfrastructure/eid-wallet/src/routes/(app)/main/+page.svelteinfrastructure/eid-wallet/src/routes/(app)/social-bindings/+page.svelteinfrastructure/eid-wallet/src/test/env-static-public.ts
Description of change
Two fixes to how the eID wallet loads binding documents. The Social Bindings full
list downloaded every counterparty's photo blobs just to render their name (~2 MB
for 8 contacts — the reported 3–4s spinner). Separately, a failed refresh silently
wiped the Legal ID and Personal cards back to their empty state.
Also clears the pages' render caches on logout — they survived it, so a
different eName onboarding on the same device was briefly shown the previous
user's name, legal ID and contacts.
No env, config or infra changes — nothing to do on staging or prod.
Issue Number
Closes #1080
Closes #1086
Type of change
pnpm testwas brokenHow the change has been tested
pnpm test: 14 passing (up from 4).svelte-check: 0 errors. Biome clean.The 4 new #1086 regression tests were confirmed to fail against the pre-fix code.
#1080 — measured, not estimated. Bytes are network-independent, so they hold
for the reporter as well as locally (the bug is invisible on localhost, where a
round trip costs ~1 ms): 257,467 → 442 bytes per contact, i.e. ~2 MB → 3.4 KB
for 8. Registry round trips for the list drop from 16 to 8.
#1086 — reproduced live, then verified. Forced a GraphQL error (HTTP 200 +
{errors, data:null}) on theid_documentquery in the running app, samestarting state, only the fix differing:
Failed to load binding documentsThe "without fix" screen matches the ticket's screenshot. The silence is the
point — it's why the ticket had no error to report.
Change checklist
Design decisions
nameOnlytrades one extra request for 250 KB, deliberately. It issues twotyped queries instead of one broad one, so the raw request count goes up — but
they run in
Promise.all(no extra round trip) and the photo blobs stop crossingthe wire. Round trips and bytes are the honest metric here; "fewer requests"
would have rejected the best fix.
resolveVaultUriis memoised inside the function, so all 7 callers benefit.5-minute TTL (a vault URI only changes on re-provisioning), concurrent callers
share one in-flight promise, failures are never cached.
#1086 was fixed by deleting a duplicate, not guarding it.
loadBindingDocumentshand-rolled a fetch that already existed asvaultGqlRequest— and the copy was the buggy one. Reusing the helper makes thebug impossible by construction, and the fix is a net negative diff.
Promise.alloverallSettledin the count-only path. Substituting an emptyresult for a failed query isn't graceful degradation when the caller feeds it into
a full-store replace — it erases marks the user still has. Also realigns the
function with its sibling, which already used
Promise.all.Detailed breakdown
#1080. The full list called
fetchNameFromVaultwithout{ nameOnly: true },taking the branch that returns every doc type from the counterparty's vault to
read one
namefield. The home screen passes the flag precisely to avoid this;the list never got it. Separately, three layers resolved the same counterparty
independently — server logs showed 16
/resolvefor 8 contacts on one load. Thepage now also caches at module level like the home screen, so revisits paint the
known list instead of a full-screen spinner; that's perceived latency only, it
removes no requests.
#1086. Two paths turned a failure into "the user has no data", then wrote it
over good state:
loadBindingDocumentschecked neitherres.oknorjson.errors,so
edgesfell back to[]andlegalIdwas nulled (verifiedderives fromisFake === false || legalId !== null, so the badge followed); and_loadPersonalBindingsCountOnlyswallowed rejections into an empty result thatreplaceAllPersonalwrote over the store wholesale. Network-level throws werealready handled correctly — only response-level failures leaked, which is why it
looked intermittent and logged nothing. Both are reachable from the 30s interval,
visibilitychangeand pull-to-refresh, so one blip visibly resets the card.Out of scope (deliberate).
SOCIAL_BINDING_DOCS_QUERYhardcodesfirst: 50with no
pageInfo, so the list is silently truncated at 50 docs — real bug, worthits own ticket. The per-binding reconcile still costs a resolve + paginated read
each; batching needs server support. No in-flight guard was added to
loadBindingDocuments: once failures throw instead of producing empty data, twoconcurrent successful runs write the same values.
Cache lifetime. The home and Social Bindings pages hold their last-known
values at module scope for instant repaints. That state outlives logout:
performLogoutleaves withgoto("/"), a client-side nav, and SvelteKit neverre-evaluates a module on client-side nav — so the next user onboarding on the
device got the previous user's data on first paint, with no spinner, since
hasEverLoadedwas cached too. Pages now register a reset from<script module>andperformLogoutcallsclearAllPageCaches(), next to thenotification and photo clears already there. Scoping the cache by eName was the
obvious alternative but can't work: the eName only arrives after an async vault
read, by which point the cache has already painted.
Summary by CodeRabbit
New Features
Bug Fixes
Tests