Fix/loading issue - #1007

Merged
coodos merged 14 commits into
mainfrom
fix/loadingIssue
Jun 5, 2026
Merged

Fix/loading issue#1007
coodos merged 14 commits into
mainfrom
fix/loadingIssue

Conversation

@Sahil2004

@Sahil2004Sahil2004 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixed the loading issue. Removed all the unecessary loading of huge data and then stripping it at frontend in favour of getting only that data which is required at frontend. Implemented skeletons for places where data is loading asynchronously. Implemented caching using LocalForge. Fixed the ordering issue when creating, editing or deleting anything. Fixed inter-user cache data leak. Brought down the image loading from several minutes to around 30s.

Issue Number

Closes#1006

Type of change

  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Photos are now cached locally for faster loading on subsequent visits.
  • Improvements

    • Enhanced loading states with visual placeholders for smoother experience.
    • Optimized personal data and photograph synchronization for better performance.
    • Improved deletion handling to prevent deleted photos from reappearing.
    • Cache automatically clears on logout for privacy.

…nd changed the query on the home page to get the count only instead of all the images in the personal documents as not required.
…red. Request data that you need instead of getting all data and then throwing it away.
@Sahil2004Sahil2004 self-assigned this Jun 4, 2026
@coderabbitai

coderabbitaiBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0790e52e-f65c-4ab5-acfb-eb3cfe9c58ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6241 and d83144b.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

📝 Walkthrough

Walkthrough

Adds IndexedDB photo caching, splits personal binding loaders (parameters, security question, photographs) with a skip-photo-blobs mode, narrows binding queries, and updates main/personal routes to hydrate progressively with skeleton UI and non-blocking parallel loads.

Changes

Progressive Photo Loading and Skeleton UI for eID Wallet

Layer / File(s)Summary
Photo cache infrastructure with localforage
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src/lib/utils/photoCache.ts
New localforage dependency; photoCache.ts provides CachedPhoto and non-fatal CRUD/filter/replace helpers persisted by metaEnvelopeId.
Split personal data loaders with skipPhotoBlobs option
infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
Adds PERSONAL_PHOTO_IDS_QUERY, three dedicated loaders (loadPersonalParameters, loadPersonalSecurityQuestion, loadPersonalPhotographs), updates loadPersonalBindings to accept { skipPhotoBlobs }, and adds a count-only _loadPersonalBindingsCountOnly using Promise.allSettled.
Binding document query optimization
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Adds BINDING_DOCS_BY_TYPE_QUERY, extends fetchNameFromVault with { nameOnly } mode to fetch only id_document/self, and narrows SOCIAL_BINDING_DOCS_QUERY to type: social_connection.
Main page parallel hydration and skeleton loading
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Adds bindingDocsLoading, refactors loadBindingDocuments to fetch id/self concurrently, deduplicates loadPersonalIntoStore() with an in-flight promise, runs initial hydration in parallel with Promise.allSettled(), and renders a binding-docs loading skeleton.
Personal page cached hydration and progressive loading
infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
Hydrates from cached photos first, then fetches parameters/security question/photos in parallel; introduces locallyDeletedIds, preserves local pending uploads, replaces store with server-visible + pending photos, syncs cache on save/delete, and renders per-photo skeletons and section placeholders while loading.
IdentityCard reactive classes
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Computes baseClasses as a reactive derived value so container classes track variant changes.
Cache invalidation on logout
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Awaits clearAllCachedPhotos() before globalState.reset() during logout to avoid stale cached images.

Sequence Diagram(s)

sequenceDiagram
participant User
participant MainPage
participant PersonalBinding
participant SocialBinding
participant PhotoCache
participant VaultAPI
User->>MainPage: Navigate to /main (login)
MainPage->>MainPage: Init bindingDocsLoading = true, show skeleton
par Parallel hydration
MainPage->>SocialBinding: fetchNameFromVault(nameOnly=true)
MainPage->>PersonalBinding: loadPersonalBindings(skipPhotoBlobs=true)
end
PersonalBinding->>PhotoCache: getCachedPhotosForEname() (immediate hydration)
PhotoCache-->>PersonalBinding: Cached photos
PersonalBinding->>MainPage: Update personal store with cached data
par Parallel network loads
SocialBinding->>VaultAPI: Fetch id_document, self binding docs
PersonalBinding->>VaultAPI: Fetch parameters, security_question, photograph ids
end
VaultAPI-->>SocialBinding: binding edges
VaultAPI-->>PersonalBinding: personal edges
PersonalBinding->>PhotoCache: Write server photos to cache
MainPage->>MainPage: bindingDocsLoading = false, render content
User->>MainPage: View personal/binding data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

evault-refactor

Suggested reviewers

  • coodos
  • sosweetham

🐰 A rabbit's ode to swifter logins:
Where once the user waited, blank and cold,
Now skeletons dance as photos load,
Cached blobs spring forth from IndexedDB's hold,
And binding docs in parallel are sowed. ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check nameStatusExplanationResolution
Title check❓ InconclusiveThe title 'Fix/loading issue' is vague and generic, using non-descriptive terms that don't convey specific information about what was actually fixed.Use a more descriptive title that highlights the main change, such as 'Implement non-blocking login with skeleton screens and LocalForage caching' or 'Optimize personal data loading with progressive UI and image caching'.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description check✅ PassedThe description covers all required template sections including issue number, type of change, testing approach, and completed checklist items with meaningful context about the changes made.
Linked Issues check✅ PassedThe PR successfully addresses issue #1006's acceptance criteria: eliminates blocking loads via parallel loading, implements skeleton screens for visual feedback, maintains data integrity, and reduces image loading time from minutes to ~30s.
Out of Scope Changes check✅ PassedAll changes align with the stated objectives: LocalForage caching implementation, skeleton UI additions, parallel data loading, cache invalidation on logout, and metadata-only photo queries are all directly aimed at resolving the blocking login load issue.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loadingIssue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sahil2004
Sahil2004 marked this pull request as ready for review June 5, 2026 08:20
@Sahil2004
Sahil2004 requested a review from coodos as a code ownerJune 5, 2026 08:20

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/photoCache.ts (2)

82-93: ⚡ Quick win

Partial-write risk in atomic cache replacement.

replaceAllCachedPhotos clears the cache and then writes all photos via Promise.all. If any single setItem call fails mid-flight, the outer catch aborts the entire operation, leaving the cache cleared but only partially refilled.

While the cache is non-fatal and the network remains the source of truth, a partial write means users see incomplete cached data (longer skeleton screens) on the next visit until the network fetch completes.

🛡️ Suggested improvement

Use Promise.allSettled instead of Promise.all so individual write failures don't abort the batch:

 export async function replaceAllCachedPhotos(
photos: CachedPhoto[],
): Promise<void> {
try {
await store.clear();
- await Promise.all(+ const results = await Promise.allSettled(
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
);
+ const failures = results.filter((r) => r.status === "rejected");+ if (failures.length > 0) {+ console.warn(`[photoCache] ${failures.length} photo(s) failed to cache`);+ }
} catch {
// non-fatal
}
}

This allows partial success—most photos are cached even if a few writes fail.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 82 - 93,
The current replaceAllCachedPhotos implementation clears the store then uses
Promise.all for store.setItem calls which can leave the cache partially refilled
if one write fails; change the writes to use Promise.allSettled so individual
setItem failures don't reject the whole batch (call store.clear(), then await
Promise.allSettled(photos.map(p => store.setItem(p.metaEnvelopeId, p))) and
optionally inspect results to log or ignore rejected entries) while keeping the
outer try/catch non-fatal.

43-48: ⚡ Quick win

Consider optimizing photo retrieval by ename.

getCachedPhotosForEname loads all cached photos via getAllCachedPhotos(), then filters by ename in memory. If the cache accumulates many photos across multiple users, this becomes inefficient.

Consider using localforage's iterate directly with early-exit or adding an IndexedDB index on ename for O(log n) lookup instead of O(n) scan.

⚡ Suggested optimization
 export async function getCachedPhotosForEname(
ename: string,
): Promise<CachedPhoto[]> {
- const all = await getAllCachedPhotos();- return all.filter((p) => p.ename === ename);+ try {+ const photos: CachedPhoto[] = [];+ await store.iterate<CachedPhoto, void>((value) => {+ if (value.ename === ename) {+ photos.push(value);+ }+ });+ return photos;+ } catch {+ return [];+ }
}

This avoids loading unrelated photos into memory. For a more robust solution, consider creating a localforage instance with an IndexedDB index on ename.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 43 - 48,
The current getCachedPhotosForEname function loads everything via
getAllCachedPhotos then filters in memory; change it to iterate the underlying
store (e.g., localforage.iterate) and collect only entries whose p.ename ===
ename, returning early if you can satisfy any expected limit, so you avoid O(n)
memory allocation; alternatively create a separate localforage instance or
IndexedDB index keyed by ename and query that store instead of calling
getAllCachedPhotos — update the implementation of getCachedPhotosForEname to use
localforage.iterate (or the new indexed store) and remove the
getAllCachedPhotos-dependent filtering.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

155-204: ⚡ Quick win

Consider using the vaultGqlRequest helper for consistency.

loadBindingDocuments constructs inline fetch() calls with manual header building and query strings (lines 155-176). The codebase already provides a vaultGqlRequest helper (imported from $lib/utils at line 42) that handles headers, error parsing, and auth tokens.

Using the helper would reduce duplication and keep the GraphQL request pattern consistent across the eID wallet module.

♻️ Suggested refactor

Replace the inline fetch with vaultGqlRequest:

+ import { vaultGqlRequest } from "$lib/utils/socialBinding";++ const BINDING_DOCS_BY_TYPE_QUERY = `+ query($type: BindingDocumentType!) {+ bindingDocuments(type: $type, first: 50) {+ edges { node { id parsed } }+ }+ }+ `;+
async function loadBindingDocuments(): Promise<void> {
if (!globalState) return;
const vault = await globalState.vaultController.vault;
if (!vault?.uri || !vault?.ename) return;
- const enameHeader = vault.ename.startsWith("@")- ? vault.ename- : `@${vault.ename}`;
const gqlUrl = new URL("/graphql", vault.uri).toString();
-- const headers: Record<string, string> = {- "Content-Type": "application/json",- "X-ENAME": enameHeader,- ...(PUBLIC_EID_WALLET_TOKEN- ? { Authorization: `Bearer ${PUBLIC_EID_WALLET_TOKEN}` }- : {}),- };- const typedQuery = (type: string) =>- JSON.stringify({- query: `query($type: BindingDocumentType!) {- bindingDocuments(type: $type, first: 50) {- edges { node { id parsed } }- }- }`,- variables: { type },- });+ const ownerEname = vault.ename;
try {
const [idDocRes, selfRes] = await Promise.all([
- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("id_document") }),- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("self") }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "id_document" }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "self" }),
]);
- const parseEdges = async (res: Response) => {- const json = await res.json();- return (json?.data?.bindingDocuments?.edges ?? []) as {- node: { id: string; parsed: ParsedBindingDoc | null };- }[];- };-- const [idDocEdges, selfEdges] = await Promise.all([- parseEdges(idDocRes),- parseEdges(selfRes),- ]);+ const idDocEdges = idDocRes.bindingDocuments?.edges ?? [];+ const selfEdges = selfRes.bindingDocuments?.edges ?? [];
const idDocEntry = idDocEdges.find((e) => e.node.parsed?.type === "id_document");
legalId = idDocEntry?.node.parsed ? toLegalIdDoc(idDocEntry.node.parsed) : null;
🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
155 - 204, Replace the two manual fetch calls and header construction in
loadBindingDocuments with the existing vaultGqlRequest helper: stop building
headers and Authorization using PUBLIC_EID_WALLET_TOKEN and instead call
vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for the
"id_document" and "self" queries (reuse the existing typedQuery string/variables
shape), then feed the returned Response/JSON into the existing parseEdges logic
(or adapt parseEdges to accept the vaultGqlRequest result). This keeps
auth/error handling consistent and removes the inline fetch/header duplication
(references: vaultGqlRequest, typedQuery, parseEdges, idDocEntry/selfEntry).
🤖 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.
Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts`:
- Around line 82-93: The current replaceAllCachedPhotos implementation clears
the store then uses Promise.all for store.setItem calls which can leave the
cache partially refilled if one write fails; change the writes to use
Promise.allSettled so individual setItem failures don't reject the whole batch
(call store.clear(), then await Promise.allSettled(photos.map(p =>
store.setItem(p.metaEnvelopeId, p))) and optionally inspect results to log or
ignore rejected entries) while keeping the outer try/catch non-fatal.
- Around line 43-48: The current getCachedPhotosForEname function loads
everything via getAllCachedPhotos then filters in memory; change it to iterate
the underlying store (e.g., localforage.iterate) and collect only entries whose
p.ename === ename, returning early if you can satisfy any expected limit, so you
avoid O(n) memory allocation; alternatively create a separate localforage
instance or IndexedDB index keyed by ename and query that store instead of
calling getAllCachedPhotos — update the implementation of
getCachedPhotosForEname to use localforage.iterate (or the new indexed store)
and remove the getAllCachedPhotos-dependent filtering.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 155-204: Replace the two manual fetch calls and header
construction in loadBindingDocuments with the existing vaultGqlRequest helper:
stop building headers and Authorization using PUBLIC_EID_WALLET_TOKEN and
instead call vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for
the "id_document" and "self" queries (reuse the existing typedQuery
string/variables shape), then feed the returned Response/JSON into the existing
parseEdges logic (or adapt parseEdges to accept the vaultGqlRequest result).
This keeps auth/error handling consistent and removes the inline fetch/header
duplication (references: vaultGqlRequest, typedQuery, parseEdges,
idDocEntry/selfEntry).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f547d467-77a0-42d7-9d93-b0263d25b9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 972f1f6 and 4fe6241.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

@coodos
coodos merged commit f31d097 into mainJun 5, 2026
4 checks passed
@coodos
coodos deleted the fix/loadingIssue branch June 5, 2026 09:31
@coderabbitaicoderabbitaiBot mentioned this pull request Jul 15, 2026
6 tasks
Sign up for freeto 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.

eID Wallet: Improve UX for blocking loads on login

2 participants

@Sahil2004@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Fix/loading issue - #1007

Merged
coodos merged 14 commits into
mainfrom
fix/loadingIssue
Jun 5, 2026
Merged

Fix/loading issue#1007
coodos merged 14 commits into
mainfrom
fix/loadingIssue

Conversation

@Sahil2004

@Sahil2004Sahil2004 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixed the loading issue. Removed all the unecessary loading of huge data and then stripping it at frontend in favour of getting only that data which is required at frontend. Implemented skeletons for places where data is loading asynchronously. Implemented caching using LocalForge. Fixed the ordering issue when creating, editing or deleting anything. Fixed inter-user cache data leak. Brought down the image loading from several minutes to around 30s.

Issue Number

Closes#1006

Type of change

  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Photos are now cached locally for faster loading on subsequent visits.
  • Improvements

    • Enhanced loading states with visual placeholders for smoother experience.
    • Optimized personal data and photograph synchronization for better performance.
    • Improved deletion handling to prevent deleted photos from reappearing.
    • Cache automatically clears on logout for privacy.

…nd changed the query on the home page to get the count only instead of all the images in the personal documents as not required.
…red. Request data that you need instead of getting all data and then throwing it away.
@Sahil2004Sahil2004 self-assigned this Jun 4, 2026
@coderabbitai

coderabbitaiBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0790e52e-f65c-4ab5-acfb-eb3cfe9c58ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6241 and d83144b.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

📝 Walkthrough

Walkthrough

Adds IndexedDB photo caching, splits personal binding loaders (parameters, security question, photographs) with a skip-photo-blobs mode, narrows binding queries, and updates main/personal routes to hydrate progressively with skeleton UI and non-blocking parallel loads.

Changes

Progressive Photo Loading and Skeleton UI for eID Wallet

Layer / File(s)Summary
Photo cache infrastructure with localforage
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src/lib/utils/photoCache.ts
New localforage dependency; photoCache.ts provides CachedPhoto and non-fatal CRUD/filter/replace helpers persisted by metaEnvelopeId.
Split personal data loaders with skipPhotoBlobs option
infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
Adds PERSONAL_PHOTO_IDS_QUERY, three dedicated loaders (loadPersonalParameters, loadPersonalSecurityQuestion, loadPersonalPhotographs), updates loadPersonalBindings to accept { skipPhotoBlobs }, and adds a count-only _loadPersonalBindingsCountOnly using Promise.allSettled.
Binding document query optimization
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Adds BINDING_DOCS_BY_TYPE_QUERY, extends fetchNameFromVault with { nameOnly } mode to fetch only id_document/self, and narrows SOCIAL_BINDING_DOCS_QUERY to type: social_connection.
Main page parallel hydration and skeleton loading
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Adds bindingDocsLoading, refactors loadBindingDocuments to fetch id/self concurrently, deduplicates loadPersonalIntoStore() with an in-flight promise, runs initial hydration in parallel with Promise.allSettled(), and renders a binding-docs loading skeleton.
Personal page cached hydration and progressive loading
infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
Hydrates from cached photos first, then fetches parameters/security question/photos in parallel; introduces locallyDeletedIds, preserves local pending uploads, replaces store with server-visible + pending photos, syncs cache on save/delete, and renders per-photo skeletons and section placeholders while loading.
IdentityCard reactive classes
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Computes baseClasses as a reactive derived value so container classes track variant changes.
Cache invalidation on logout
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Awaits clearAllCachedPhotos() before globalState.reset() during logout to avoid stale cached images.

Sequence Diagram(s)

sequenceDiagram
participant User
participant MainPage
participant PersonalBinding
participant SocialBinding
participant PhotoCache
participant VaultAPI
User->>MainPage: Navigate to /main (login)
MainPage->>MainPage: Init bindingDocsLoading = true, show skeleton
par Parallel hydration
MainPage->>SocialBinding: fetchNameFromVault(nameOnly=true)
MainPage->>PersonalBinding: loadPersonalBindings(skipPhotoBlobs=true)
end
PersonalBinding->>PhotoCache: getCachedPhotosForEname() (immediate hydration)
PhotoCache-->>PersonalBinding: Cached photos
PersonalBinding->>MainPage: Update personal store with cached data
par Parallel network loads
SocialBinding->>VaultAPI: Fetch id_document, self binding docs
PersonalBinding->>VaultAPI: Fetch parameters, security_question, photograph ids
end
VaultAPI-->>SocialBinding: binding edges
VaultAPI-->>PersonalBinding: personal edges
PersonalBinding->>PhotoCache: Write server photos to cache
MainPage->>MainPage: bindingDocsLoading = false, render content
User->>MainPage: View personal/binding data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

evault-refactor

Suggested reviewers

  • coodos
  • sosweetham

🐰 A rabbit's ode to swifter logins:
Where once the user waited, blank and cold,
Now skeletons dance as photos load,
Cached blobs spring forth from IndexedDB's hold,
And binding docs in parallel are sowed. ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check nameStatusExplanationResolution
Title check❓ InconclusiveThe title 'Fix/loading issue' is vague and generic, using non-descriptive terms that don't convey specific information about what was actually fixed.Use a more descriptive title that highlights the main change, such as 'Implement non-blocking login with skeleton screens and LocalForage caching' or 'Optimize personal data loading with progressive UI and image caching'.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description check✅ PassedThe description covers all required template sections including issue number, type of change, testing approach, and completed checklist items with meaningful context about the changes made.
Linked Issues check✅ PassedThe PR successfully addresses issue #1006's acceptance criteria: eliminates blocking loads via parallel loading, implements skeleton screens for visual feedback, maintains data integrity, and reduces image loading time from minutes to ~30s.
Out of Scope Changes check✅ PassedAll changes align with the stated objectives: LocalForage caching implementation, skeleton UI additions, parallel data loading, cache invalidation on logout, and metadata-only photo queries are all directly aimed at resolving the blocking login load issue.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loadingIssue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sahil2004
Sahil2004 marked this pull request as ready for review June 5, 2026 08:20
@Sahil2004
Sahil2004 requested a review from coodos as a code ownerJune 5, 2026 08:20

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/photoCache.ts (2)

82-93: ⚡ Quick win

Partial-write risk in atomic cache replacement.

replaceAllCachedPhotos clears the cache and then writes all photos via Promise.all. If any single setItem call fails mid-flight, the outer catch aborts the entire operation, leaving the cache cleared but only partially refilled.

While the cache is non-fatal and the network remains the source of truth, a partial write means users see incomplete cached data (longer skeleton screens) on the next visit until the network fetch completes.

🛡️ Suggested improvement

Use Promise.allSettled instead of Promise.all so individual write failures don't abort the batch:

 export async function replaceAllCachedPhotos(
photos: CachedPhoto[],
): Promise<void> {
try {
await store.clear();
- await Promise.all(+ const results = await Promise.allSettled(
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
);
+ const failures = results.filter((r) => r.status === "rejected");+ if (failures.length > 0) {+ console.warn(`[photoCache] ${failures.length} photo(s) failed to cache`);+ }
} catch {
// non-fatal
}
}

This allows partial success—most photos are cached even if a few writes fail.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 82 - 93,
The current replaceAllCachedPhotos implementation clears the store then uses
Promise.all for store.setItem calls which can leave the cache partially refilled
if one write fails; change the writes to use Promise.allSettled so individual
setItem failures don't reject the whole batch (call store.clear(), then await
Promise.allSettled(photos.map(p => store.setItem(p.metaEnvelopeId, p))) and
optionally inspect results to log or ignore rejected entries) while keeping the
outer try/catch non-fatal.

43-48: ⚡ Quick win

Consider optimizing photo retrieval by ename.

getCachedPhotosForEname loads all cached photos via getAllCachedPhotos(), then filters by ename in memory. If the cache accumulates many photos across multiple users, this becomes inefficient.

Consider using localforage's iterate directly with early-exit or adding an IndexedDB index on ename for O(log n) lookup instead of O(n) scan.

⚡ Suggested optimization
 export async function getCachedPhotosForEname(
ename: string,
): Promise<CachedPhoto[]> {
- const all = await getAllCachedPhotos();- return all.filter((p) => p.ename === ename);+ try {+ const photos: CachedPhoto[] = [];+ await store.iterate<CachedPhoto, void>((value) => {+ if (value.ename === ename) {+ photos.push(value);+ }+ });+ return photos;+ } catch {+ return [];+ }
}

This avoids loading unrelated photos into memory. For a more robust solution, consider creating a localforage instance with an IndexedDB index on ename.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 43 - 48,
The current getCachedPhotosForEname function loads everything via
getAllCachedPhotos then filters in memory; change it to iterate the underlying
store (e.g., localforage.iterate) and collect only entries whose p.ename ===
ename, returning early if you can satisfy any expected limit, so you avoid O(n)
memory allocation; alternatively create a separate localforage instance or
IndexedDB index keyed by ename and query that store instead of calling
getAllCachedPhotos — update the implementation of getCachedPhotosForEname to use
localforage.iterate (or the new indexed store) and remove the
getAllCachedPhotos-dependent filtering.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

155-204: ⚡ Quick win

Consider using the vaultGqlRequest helper for consistency.

loadBindingDocuments constructs inline fetch() calls with manual header building and query strings (lines 155-176). The codebase already provides a vaultGqlRequest helper (imported from $lib/utils at line 42) that handles headers, error parsing, and auth tokens.

Using the helper would reduce duplication and keep the GraphQL request pattern consistent across the eID wallet module.

♻️ Suggested refactor

Replace the inline fetch with vaultGqlRequest:

+ import { vaultGqlRequest } from "$lib/utils/socialBinding";++ const BINDING_DOCS_BY_TYPE_QUERY = `+ query($type: BindingDocumentType!) {+ bindingDocuments(type: $type, first: 50) {+ edges { node { id parsed } }+ }+ }+ `;+
async function loadBindingDocuments(): Promise<void> {
if (!globalState) return;
const vault = await globalState.vaultController.vault;
if (!vault?.uri || !vault?.ename) return;
- const enameHeader = vault.ename.startsWith("@")- ? vault.ename- : `@${vault.ename}`;
const gqlUrl = new URL("/graphql", vault.uri).toString();
-- const headers: Record<string, string> = {- "Content-Type": "application/json",- "X-ENAME": enameHeader,- ...(PUBLIC_EID_WALLET_TOKEN- ? { Authorization: `Bearer ${PUBLIC_EID_WALLET_TOKEN}` }- : {}),- };- const typedQuery = (type: string) =>- JSON.stringify({- query: `query($type: BindingDocumentType!) {- bindingDocuments(type: $type, first: 50) {- edges { node { id parsed } }- }- }`,- variables: { type },- });+ const ownerEname = vault.ename;
try {
const [idDocRes, selfRes] = await Promise.all([
- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("id_document") }),- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("self") }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "id_document" }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "self" }),
]);
- const parseEdges = async (res: Response) => {- const json = await res.json();- return (json?.data?.bindingDocuments?.edges ?? []) as {- node: { id: string; parsed: ParsedBindingDoc | null };- }[];- };-- const [idDocEdges, selfEdges] = await Promise.all([- parseEdges(idDocRes),- parseEdges(selfRes),- ]);+ const idDocEdges = idDocRes.bindingDocuments?.edges ?? [];+ const selfEdges = selfRes.bindingDocuments?.edges ?? [];
const idDocEntry = idDocEdges.find((e) => e.node.parsed?.type === "id_document");
legalId = idDocEntry?.node.parsed ? toLegalIdDoc(idDocEntry.node.parsed) : null;
🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
155 - 204, Replace the two manual fetch calls and header construction in
loadBindingDocuments with the existing vaultGqlRequest helper: stop building
headers and Authorization using PUBLIC_EID_WALLET_TOKEN and instead call
vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for the
"id_document" and "self" queries (reuse the existing typedQuery string/variables
shape), then feed the returned Response/JSON into the existing parseEdges logic
(or adapt parseEdges to accept the vaultGqlRequest result). This keeps
auth/error handling consistent and removes the inline fetch/header duplication
(references: vaultGqlRequest, typedQuery, parseEdges, idDocEntry/selfEntry).
🤖 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.
Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts`:
- Around line 82-93: The current replaceAllCachedPhotos implementation clears
the store then uses Promise.all for store.setItem calls which can leave the
cache partially refilled if one write fails; change the writes to use
Promise.allSettled so individual setItem failures don't reject the whole batch
(call store.clear(), then await Promise.allSettled(photos.map(p =>
store.setItem(p.metaEnvelopeId, p))) and optionally inspect results to log or
ignore rejected entries) while keeping the outer try/catch non-fatal.
- Around line 43-48: The current getCachedPhotosForEname function loads
everything via getAllCachedPhotos then filters in memory; change it to iterate
the underlying store (e.g., localforage.iterate) and collect only entries whose
p.ename === ename, returning early if you can satisfy any expected limit, so you
avoid O(n) memory allocation; alternatively create a separate localforage
instance or IndexedDB index keyed by ename and query that store instead of
calling getAllCachedPhotos — update the implementation of
getCachedPhotosForEname to use localforage.iterate (or the new indexed store)
and remove the getAllCachedPhotos-dependent filtering.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 155-204: Replace the two manual fetch calls and header
construction in loadBindingDocuments with the existing vaultGqlRequest helper:
stop building headers and Authorization using PUBLIC_EID_WALLET_TOKEN and
instead call vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for
the "id_document" and "self" queries (reuse the existing typedQuery
string/variables shape), then feed the returned Response/JSON into the existing
parseEdges logic (or adapt parseEdges to accept the vaultGqlRequest result).
This keeps auth/error handling consistent and removes the inline fetch/header
duplication (references: vaultGqlRequest, typedQuery, parseEdges,
idDocEntry/selfEntry).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f547d467-77a0-42d7-9d93-b0263d25b9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 972f1f6 and 4fe6241.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

@coodos
coodos merged commit f31d097 into mainJun 5, 2026
4 checks passed
@coodos
coodos deleted the fix/loadingIssue branch June 5, 2026 09:31
@coderabbitaicoderabbitaiBot mentioned this pull request Jul 15, 2026
6 tasks
Sign up for freeto 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.

eID Wallet: Improve UX for blocking loads on login

2 participants

@Sahil2004@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix/loading issue - #1007

Merged
coodos merged 14 commits into
mainfrom
fix/loadingIssue
Jun 5, 2026
Merged

Fix/loading issue#1007
coodos merged 14 commits into
mainfrom
fix/loadingIssue

Conversation

@Sahil2004

@Sahil2004Sahil2004 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixed the loading issue. Removed all the unecessary loading of huge data and then stripping it at frontend in favour of getting only that data which is required at frontend. Implemented skeletons for places where data is loading asynchronously. Implemented caching using LocalForge. Fixed the ordering issue when creating, editing or deleting anything. Fixed inter-user cache data leak. Brought down the image loading from several minutes to around 30s.

Issue Number

Closes#1006

Type of change

  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Photos are now cached locally for faster loading on subsequent visits.
  • Improvements

    • Enhanced loading states with visual placeholders for smoother experience.
    • Optimized personal data and photograph synchronization for better performance.
    • Improved deletion handling to prevent deleted photos from reappearing.
    • Cache automatically clears on logout for privacy.

…nd changed the query on the home page to get the count only instead of all the images in the personal documents as not required.
…red. Request data that you need instead of getting all data and then throwing it away.
@Sahil2004Sahil2004 self-assigned this Jun 4, 2026
@coderabbitai

coderabbitaiBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0790e52e-f65c-4ab5-acfb-eb3cfe9c58ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6241 and d83144b.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

📝 Walkthrough

Walkthrough

Adds IndexedDB photo caching, splits personal binding loaders (parameters, security question, photographs) with a skip-photo-blobs mode, narrows binding queries, and updates main/personal routes to hydrate progressively with skeleton UI and non-blocking parallel loads.

Changes

Progressive Photo Loading and Skeleton UI for eID Wallet

Layer / File(s)Summary
Photo cache infrastructure with localforage
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src/lib/utils/photoCache.ts
New localforage dependency; photoCache.ts provides CachedPhoto and non-fatal CRUD/filter/replace helpers persisted by metaEnvelopeId.
Split personal data loaders with skipPhotoBlobs option
infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
Adds PERSONAL_PHOTO_IDS_QUERY, three dedicated loaders (loadPersonalParameters, loadPersonalSecurityQuestion, loadPersonalPhotographs), updates loadPersonalBindings to accept { skipPhotoBlobs }, and adds a count-only _loadPersonalBindingsCountOnly using Promise.allSettled.
Binding document query optimization
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Adds BINDING_DOCS_BY_TYPE_QUERY, extends fetchNameFromVault with { nameOnly } mode to fetch only id_document/self, and narrows SOCIAL_BINDING_DOCS_QUERY to type: social_connection.
Main page parallel hydration and skeleton loading
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Adds bindingDocsLoading, refactors loadBindingDocuments to fetch id/self concurrently, deduplicates loadPersonalIntoStore() with an in-flight promise, runs initial hydration in parallel with Promise.allSettled(), and renders a binding-docs loading skeleton.
Personal page cached hydration and progressive loading
infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
Hydrates from cached photos first, then fetches parameters/security question/photos in parallel; introduces locallyDeletedIds, preserves local pending uploads, replaces store with server-visible + pending photos, syncs cache on save/delete, and renders per-photo skeletons and section placeholders while loading.
IdentityCard reactive classes
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Computes baseClasses as a reactive derived value so container classes track variant changes.
Cache invalidation on logout
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Awaits clearAllCachedPhotos() before globalState.reset() during logout to avoid stale cached images.

Sequence Diagram(s)

sequenceDiagram
participant User
participant MainPage
participant PersonalBinding
participant SocialBinding
participant PhotoCache
participant VaultAPI
User->>MainPage: Navigate to /main (login)
MainPage->>MainPage: Init bindingDocsLoading = true, show skeleton
par Parallel hydration
MainPage->>SocialBinding: fetchNameFromVault(nameOnly=true)
MainPage->>PersonalBinding: loadPersonalBindings(skipPhotoBlobs=true)
end
PersonalBinding->>PhotoCache: getCachedPhotosForEname() (immediate hydration)
PhotoCache-->>PersonalBinding: Cached photos
PersonalBinding->>MainPage: Update personal store with cached data
par Parallel network loads
SocialBinding->>VaultAPI: Fetch id_document, self binding docs
PersonalBinding->>VaultAPI: Fetch parameters, security_question, photograph ids
end
VaultAPI-->>SocialBinding: binding edges
VaultAPI-->>PersonalBinding: personal edges
PersonalBinding->>PhotoCache: Write server photos to cache
MainPage->>MainPage: bindingDocsLoading = false, render content
User->>MainPage: View personal/binding data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

evault-refactor

Suggested reviewers

  • coodos
  • sosweetham

🐰 A rabbit's ode to swifter logins:
Where once the user waited, blank and cold,
Now skeletons dance as photos load,
Cached blobs spring forth from IndexedDB's hold,
And binding docs in parallel are sowed. ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check nameStatusExplanationResolution
Title check❓ InconclusiveThe title 'Fix/loading issue' is vague and generic, using non-descriptive terms that don't convey specific information about what was actually fixed.Use a more descriptive title that highlights the main change, such as 'Implement non-blocking login with skeleton screens and LocalForage caching' or 'Optimize personal data loading with progressive UI and image caching'.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description check✅ PassedThe description covers all required template sections including issue number, type of change, testing approach, and completed checklist items with meaningful context about the changes made.
Linked Issues check✅ PassedThe PR successfully addresses issue #1006's acceptance criteria: eliminates blocking loads via parallel loading, implements skeleton screens for visual feedback, maintains data integrity, and reduces image loading time from minutes to ~30s.
Out of Scope Changes check✅ PassedAll changes align with the stated objectives: LocalForage caching implementation, skeleton UI additions, parallel data loading, cache invalidation on logout, and metadata-only photo queries are all directly aimed at resolving the blocking login load issue.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loadingIssue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sahil2004
Sahil2004 marked this pull request as ready for review June 5, 2026 08:20
@Sahil2004
Sahil2004 requested a review from coodos as a code ownerJune 5, 2026 08:20

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/photoCache.ts (2)

82-93: ⚡ Quick win

Partial-write risk in atomic cache replacement.

replaceAllCachedPhotos clears the cache and then writes all photos via Promise.all. If any single setItem call fails mid-flight, the outer catch aborts the entire operation, leaving the cache cleared but only partially refilled.

While the cache is non-fatal and the network remains the source of truth, a partial write means users see incomplete cached data (longer skeleton screens) on the next visit until the network fetch completes.

🛡️ Suggested improvement

Use Promise.allSettled instead of Promise.all so individual write failures don't abort the batch:

 export async function replaceAllCachedPhotos(
photos: CachedPhoto[],
): Promise<void> {
try {
await store.clear();
- await Promise.all(+ const results = await Promise.allSettled(
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
);
+ const failures = results.filter((r) => r.status === "rejected");+ if (failures.length > 0) {+ console.warn(`[photoCache] ${failures.length} photo(s) failed to cache`);+ }
} catch {
// non-fatal
}
}

This allows partial success—most photos are cached even if a few writes fail.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 82 - 93,
The current replaceAllCachedPhotos implementation clears the store then uses
Promise.all for store.setItem calls which can leave the cache partially refilled
if one write fails; change the writes to use Promise.allSettled so individual
setItem failures don't reject the whole batch (call store.clear(), then await
Promise.allSettled(photos.map(p => store.setItem(p.metaEnvelopeId, p))) and
optionally inspect results to log or ignore rejected entries) while keeping the
outer try/catch non-fatal.

43-48: ⚡ Quick win

Consider optimizing photo retrieval by ename.

getCachedPhotosForEname loads all cached photos via getAllCachedPhotos(), then filters by ename in memory. If the cache accumulates many photos across multiple users, this becomes inefficient.

Consider using localforage's iterate directly with early-exit or adding an IndexedDB index on ename for O(log n) lookup instead of O(n) scan.

⚡ Suggested optimization
 export async function getCachedPhotosForEname(
ename: string,
): Promise<CachedPhoto[]> {
- const all = await getAllCachedPhotos();- return all.filter((p) => p.ename === ename);+ try {+ const photos: CachedPhoto[] = [];+ await store.iterate<CachedPhoto, void>((value) => {+ if (value.ename === ename) {+ photos.push(value);+ }+ });+ return photos;+ } catch {+ return [];+ }
}

This avoids loading unrelated photos into memory. For a more robust solution, consider creating a localforage instance with an IndexedDB index on ename.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 43 - 48,
The current getCachedPhotosForEname function loads everything via
getAllCachedPhotos then filters in memory; change it to iterate the underlying
store (e.g., localforage.iterate) and collect only entries whose p.ename ===
ename, returning early if you can satisfy any expected limit, so you avoid O(n)
memory allocation; alternatively create a separate localforage instance or
IndexedDB index keyed by ename and query that store instead of calling
getAllCachedPhotos — update the implementation of getCachedPhotosForEname to use
localforage.iterate (or the new indexed store) and remove the
getAllCachedPhotos-dependent filtering.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

155-204: ⚡ Quick win

Consider using the vaultGqlRequest helper for consistency.

loadBindingDocuments constructs inline fetch() calls with manual header building and query strings (lines 155-176). The codebase already provides a vaultGqlRequest helper (imported from $lib/utils at line 42) that handles headers, error parsing, and auth tokens.

Using the helper would reduce duplication and keep the GraphQL request pattern consistent across the eID wallet module.

♻️ Suggested refactor

Replace the inline fetch with vaultGqlRequest:

+ import { vaultGqlRequest } from "$lib/utils/socialBinding";++ const BINDING_DOCS_BY_TYPE_QUERY = `+ query($type: BindingDocumentType!) {+ bindingDocuments(type: $type, first: 50) {+ edges { node { id parsed } }+ }+ }+ `;+
async function loadBindingDocuments(): Promise<void> {
if (!globalState) return;
const vault = await globalState.vaultController.vault;
if (!vault?.uri || !vault?.ename) return;
- const enameHeader = vault.ename.startsWith("@")- ? vault.ename- : `@${vault.ename}`;
const gqlUrl = new URL("/graphql", vault.uri).toString();
-- const headers: Record<string, string> = {- "Content-Type": "application/json",- "X-ENAME": enameHeader,- ...(PUBLIC_EID_WALLET_TOKEN- ? { Authorization: `Bearer ${PUBLIC_EID_WALLET_TOKEN}` }- : {}),- };- const typedQuery = (type: string) =>- JSON.stringify({- query: `query($type: BindingDocumentType!) {- bindingDocuments(type: $type, first: 50) {- edges { node { id parsed } }- }- }`,- variables: { type },- });+ const ownerEname = vault.ename;
try {
const [idDocRes, selfRes] = await Promise.all([
- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("id_document") }),- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("self") }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "id_document" }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "self" }),
]);
- const parseEdges = async (res: Response) => {- const json = await res.json();- return (json?.data?.bindingDocuments?.edges ?? []) as {- node: { id: string; parsed: ParsedBindingDoc | null };- }[];- };-- const [idDocEdges, selfEdges] = await Promise.all([- parseEdges(idDocRes),- parseEdges(selfRes),- ]);+ const idDocEdges = idDocRes.bindingDocuments?.edges ?? [];+ const selfEdges = selfRes.bindingDocuments?.edges ?? [];
const idDocEntry = idDocEdges.find((e) => e.node.parsed?.type === "id_document");
legalId = idDocEntry?.node.parsed ? toLegalIdDoc(idDocEntry.node.parsed) : null;
🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
155 - 204, Replace the two manual fetch calls and header construction in
loadBindingDocuments with the existing vaultGqlRequest helper: stop building
headers and Authorization using PUBLIC_EID_WALLET_TOKEN and instead call
vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for the
"id_document" and "self" queries (reuse the existing typedQuery string/variables
shape), then feed the returned Response/JSON into the existing parseEdges logic
(or adapt parseEdges to accept the vaultGqlRequest result). This keeps
auth/error handling consistent and removes the inline fetch/header duplication
(references: vaultGqlRequest, typedQuery, parseEdges, idDocEntry/selfEntry).
🤖 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.
Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts`:
- Around line 82-93: The current replaceAllCachedPhotos implementation clears
the store then uses Promise.all for store.setItem calls which can leave the
cache partially refilled if one write fails; change the writes to use
Promise.allSettled so individual setItem failures don't reject the whole batch
(call store.clear(), then await Promise.allSettled(photos.map(p =>
store.setItem(p.metaEnvelopeId, p))) and optionally inspect results to log or
ignore rejected entries) while keeping the outer try/catch non-fatal.
- Around line 43-48: The current getCachedPhotosForEname function loads
everything via getAllCachedPhotos then filters in memory; change it to iterate
the underlying store (e.g., localforage.iterate) and collect only entries whose
p.ename === ename, returning early if you can satisfy any expected limit, so you
avoid O(n) memory allocation; alternatively create a separate localforage
instance or IndexedDB index keyed by ename and query that store instead of
calling getAllCachedPhotos — update the implementation of
getCachedPhotosForEname to use localforage.iterate (or the new indexed store)
and remove the getAllCachedPhotos-dependent filtering.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 155-204: Replace the two manual fetch calls and header
construction in loadBindingDocuments with the existing vaultGqlRequest helper:
stop building headers and Authorization using PUBLIC_EID_WALLET_TOKEN and
instead call vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for
the "id_document" and "self" queries (reuse the existing typedQuery
string/variables shape), then feed the returned Response/JSON into the existing
parseEdges logic (or adapt parseEdges to accept the vaultGqlRequest result).
This keeps auth/error handling consistent and removes the inline fetch/header
duplication (references: vaultGqlRequest, typedQuery, parseEdges,
idDocEntry/selfEntry).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f547d467-77a0-42d7-9d93-b0263d25b9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 972f1f6 and 4fe6241.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

@coodos
coodos merged commit f31d097 into mainJun 5, 2026
4 checks passed
@coodos
coodos deleted the fix/loadingIssue branch June 5, 2026 09:31
@coderabbitaicoderabbitaiBot mentioned this pull request Jul 15, 2026
6 tasks
Sign up for freeto 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.

eID Wallet: Improve UX for blocking loads on login

2 participants

@Sahil2004@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix/loading issue - #1007

Merged
coodos merged 14 commits into
mainfrom
fix/loadingIssue
Jun 5, 2026
Merged

Fix/loading issue#1007
coodos merged 14 commits into
mainfrom
fix/loadingIssue

Conversation

@Sahil2004

@Sahil2004Sahil2004 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixed the loading issue. Removed all the unecessary loading of huge data and then stripping it at frontend in favour of getting only that data which is required at frontend. Implemented skeletons for places where data is loading asynchronously. Implemented caching using LocalForge. Fixed the ordering issue when creating, editing or deleting anything. Fixed inter-user cache data leak. Brought down the image loading from several minutes to around 30s.

Issue Number

Closes#1006

Type of change

  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Photos are now cached locally for faster loading on subsequent visits.
  • Improvements

    • Enhanced loading states with visual placeholders for smoother experience.
    • Optimized personal data and photograph synchronization for better performance.
    • Improved deletion handling to prevent deleted photos from reappearing.
    • Cache automatically clears on logout for privacy.

…nd changed the query on the home page to get the count only instead of all the images in the personal documents as not required.
…red. Request data that you need instead of getting all data and then throwing it away.
@Sahil2004Sahil2004 self-assigned this Jun 4, 2026
@coderabbitai

coderabbitaiBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0790e52e-f65c-4ab5-acfb-eb3cfe9c58ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6241 and d83144b.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

📝 Walkthrough

Walkthrough

Adds IndexedDB photo caching, splits personal binding loaders (parameters, security question, photographs) with a skip-photo-blobs mode, narrows binding queries, and updates main/personal routes to hydrate progressively with skeleton UI and non-blocking parallel loads.

Changes

Progressive Photo Loading and Skeleton UI for eID Wallet

Layer / File(s)Summary
Photo cache infrastructure with localforage
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src/lib/utils/photoCache.ts
New localforage dependency; photoCache.ts provides CachedPhoto and non-fatal CRUD/filter/replace helpers persisted by metaEnvelopeId.
Split personal data loaders with skipPhotoBlobs option
infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
Adds PERSONAL_PHOTO_IDS_QUERY, three dedicated loaders (loadPersonalParameters, loadPersonalSecurityQuestion, loadPersonalPhotographs), updates loadPersonalBindings to accept { skipPhotoBlobs }, and adds a count-only _loadPersonalBindingsCountOnly using Promise.allSettled.
Binding document query optimization
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Adds BINDING_DOCS_BY_TYPE_QUERY, extends fetchNameFromVault with { nameOnly } mode to fetch only id_document/self, and narrows SOCIAL_BINDING_DOCS_QUERY to type: social_connection.
Main page parallel hydration and skeleton loading
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Adds bindingDocsLoading, refactors loadBindingDocuments to fetch id/self concurrently, deduplicates loadPersonalIntoStore() with an in-flight promise, runs initial hydration in parallel with Promise.allSettled(), and renders a binding-docs loading skeleton.
Personal page cached hydration and progressive loading
infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
Hydrates from cached photos first, then fetches parameters/security question/photos in parallel; introduces locallyDeletedIds, preserves local pending uploads, replaces store with server-visible + pending photos, syncs cache on save/delete, and renders per-photo skeletons and section placeholders while loading.
IdentityCard reactive classes
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Computes baseClasses as a reactive derived value so container classes track variant changes.
Cache invalidation on logout
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Awaits clearAllCachedPhotos() before globalState.reset() during logout to avoid stale cached images.

Sequence Diagram(s)

sequenceDiagram
participant User
participant MainPage
participant PersonalBinding
participant SocialBinding
participant PhotoCache
participant VaultAPI
User->>MainPage: Navigate to /main (login)
MainPage->>MainPage: Init bindingDocsLoading = true, show skeleton
par Parallel hydration
MainPage->>SocialBinding: fetchNameFromVault(nameOnly=true)
MainPage->>PersonalBinding: loadPersonalBindings(skipPhotoBlobs=true)
end
PersonalBinding->>PhotoCache: getCachedPhotosForEname() (immediate hydration)
PhotoCache-->>PersonalBinding: Cached photos
PersonalBinding->>MainPage: Update personal store with cached data
par Parallel network loads
SocialBinding->>VaultAPI: Fetch id_document, self binding docs
PersonalBinding->>VaultAPI: Fetch parameters, security_question, photograph ids
end
VaultAPI-->>SocialBinding: binding edges
VaultAPI-->>PersonalBinding: personal edges
PersonalBinding->>PhotoCache: Write server photos to cache
MainPage->>MainPage: bindingDocsLoading = false, render content
User->>MainPage: View personal/binding data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

evault-refactor

Suggested reviewers

  • coodos
  • sosweetham

🐰 A rabbit's ode to swifter logins:
Where once the user waited, blank and cold,
Now skeletons dance as photos load,
Cached blobs spring forth from IndexedDB's hold,
And binding docs in parallel are sowed. ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check nameStatusExplanationResolution
Title check❓ InconclusiveThe title 'Fix/loading issue' is vague and generic, using non-descriptive terms that don't convey specific information about what was actually fixed.Use a more descriptive title that highlights the main change, such as 'Implement non-blocking login with skeleton screens and LocalForage caching' or 'Optimize personal data loading with progressive UI and image caching'.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description check✅ PassedThe description covers all required template sections including issue number, type of change, testing approach, and completed checklist items with meaningful context about the changes made.
Linked Issues check✅ PassedThe PR successfully addresses issue #1006's acceptance criteria: eliminates blocking loads via parallel loading, implements skeleton screens for visual feedback, maintains data integrity, and reduces image loading time from minutes to ~30s.
Out of Scope Changes check✅ PassedAll changes align with the stated objectives: LocalForage caching implementation, skeleton UI additions, parallel data loading, cache invalidation on logout, and metadata-only photo queries are all directly aimed at resolving the blocking login load issue.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loadingIssue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sahil2004
Sahil2004 marked this pull request as ready for review June 5, 2026 08:20
@Sahil2004
Sahil2004 requested a review from coodos as a code ownerJune 5, 2026 08:20

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/photoCache.ts (2)

82-93: ⚡ Quick win

Partial-write risk in atomic cache replacement.

replaceAllCachedPhotos clears the cache and then writes all photos via Promise.all. If any single setItem call fails mid-flight, the outer catch aborts the entire operation, leaving the cache cleared but only partially refilled.

While the cache is non-fatal and the network remains the source of truth, a partial write means users see incomplete cached data (longer skeleton screens) on the next visit until the network fetch completes.

🛡️ Suggested improvement

Use Promise.allSettled instead of Promise.all so individual write failures don't abort the batch:

 export async function replaceAllCachedPhotos(
photos: CachedPhoto[],
): Promise<void> {
try {
await store.clear();
- await Promise.all(+ const results = await Promise.allSettled(
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
);
+ const failures = results.filter((r) => r.status === "rejected");+ if (failures.length > 0) {+ console.warn(`[photoCache] ${failures.length} photo(s) failed to cache`);+ }
} catch {
// non-fatal
}
}

This allows partial success—most photos are cached even if a few writes fail.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 82 - 93,
The current replaceAllCachedPhotos implementation clears the store then uses
Promise.all for store.setItem calls which can leave the cache partially refilled
if one write fails; change the writes to use Promise.allSettled so individual
setItem failures don't reject the whole batch (call store.clear(), then await
Promise.allSettled(photos.map(p => store.setItem(p.metaEnvelopeId, p))) and
optionally inspect results to log or ignore rejected entries) while keeping the
outer try/catch non-fatal.

43-48: ⚡ Quick win

Consider optimizing photo retrieval by ename.

getCachedPhotosForEname loads all cached photos via getAllCachedPhotos(), then filters by ename in memory. If the cache accumulates many photos across multiple users, this becomes inefficient.

Consider using localforage's iterate directly with early-exit or adding an IndexedDB index on ename for O(log n) lookup instead of O(n) scan.

⚡ Suggested optimization
 export async function getCachedPhotosForEname(
ename: string,
): Promise<CachedPhoto[]> {
- const all = await getAllCachedPhotos();- return all.filter((p) => p.ename === ename);+ try {+ const photos: CachedPhoto[] = [];+ await store.iterate<CachedPhoto, void>((value) => {+ if (value.ename === ename) {+ photos.push(value);+ }+ });+ return photos;+ } catch {+ return [];+ }
}

This avoids loading unrelated photos into memory. For a more robust solution, consider creating a localforage instance with an IndexedDB index on ename.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 43 - 48,
The current getCachedPhotosForEname function loads everything via
getAllCachedPhotos then filters in memory; change it to iterate the underlying
store (e.g., localforage.iterate) and collect only entries whose p.ename ===
ename, returning early if you can satisfy any expected limit, so you avoid O(n)
memory allocation; alternatively create a separate localforage instance or
IndexedDB index keyed by ename and query that store instead of calling
getAllCachedPhotos — update the implementation of getCachedPhotosForEname to use
localforage.iterate (or the new indexed store) and remove the
getAllCachedPhotos-dependent filtering.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

155-204: ⚡ Quick win

Consider using the vaultGqlRequest helper for consistency.

loadBindingDocuments constructs inline fetch() calls with manual header building and query strings (lines 155-176). The codebase already provides a vaultGqlRequest helper (imported from $lib/utils at line 42) that handles headers, error parsing, and auth tokens.

Using the helper would reduce duplication and keep the GraphQL request pattern consistent across the eID wallet module.

♻️ Suggested refactor

Replace the inline fetch with vaultGqlRequest:

+ import { vaultGqlRequest } from "$lib/utils/socialBinding";++ const BINDING_DOCS_BY_TYPE_QUERY = `+ query($type: BindingDocumentType!) {+ bindingDocuments(type: $type, first: 50) {+ edges { node { id parsed } }+ }+ }+ `;+
async function loadBindingDocuments(): Promise<void> {
if (!globalState) return;
const vault = await globalState.vaultController.vault;
if (!vault?.uri || !vault?.ename) return;
- const enameHeader = vault.ename.startsWith("@")- ? vault.ename- : `@${vault.ename}`;
const gqlUrl = new URL("/graphql", vault.uri).toString();
-- const headers: Record<string, string> = {- "Content-Type": "application/json",- "X-ENAME": enameHeader,- ...(PUBLIC_EID_WALLET_TOKEN- ? { Authorization: `Bearer ${PUBLIC_EID_WALLET_TOKEN}` }- : {}),- };- const typedQuery = (type: string) =>- JSON.stringify({- query: `query($type: BindingDocumentType!) {- bindingDocuments(type: $type, first: 50) {- edges { node { id parsed } }- }- }`,- variables: { type },- });+ const ownerEname = vault.ename;
try {
const [idDocRes, selfRes] = await Promise.all([
- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("id_document") }),- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("self") }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "id_document" }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "self" }),
]);
- const parseEdges = async (res: Response) => {- const json = await res.json();- return (json?.data?.bindingDocuments?.edges ?? []) as {- node: { id: string; parsed: ParsedBindingDoc | null };- }[];- };-- const [idDocEdges, selfEdges] = await Promise.all([- parseEdges(idDocRes),- parseEdges(selfRes),- ]);+ const idDocEdges = idDocRes.bindingDocuments?.edges ?? [];+ const selfEdges = selfRes.bindingDocuments?.edges ?? [];
const idDocEntry = idDocEdges.find((e) => e.node.parsed?.type === "id_document");
legalId = idDocEntry?.node.parsed ? toLegalIdDoc(idDocEntry.node.parsed) : null;
🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
155 - 204, Replace the two manual fetch calls and header construction in
loadBindingDocuments with the existing vaultGqlRequest helper: stop building
headers and Authorization using PUBLIC_EID_WALLET_TOKEN and instead call
vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for the
"id_document" and "self" queries (reuse the existing typedQuery string/variables
shape), then feed the returned Response/JSON into the existing parseEdges logic
(or adapt parseEdges to accept the vaultGqlRequest result). This keeps
auth/error handling consistent and removes the inline fetch/header duplication
(references: vaultGqlRequest, typedQuery, parseEdges, idDocEntry/selfEntry).
🤖 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.
Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts`:
- Around line 82-93: The current replaceAllCachedPhotos implementation clears
the store then uses Promise.all for store.setItem calls which can leave the
cache partially refilled if one write fails; change the writes to use
Promise.allSettled so individual setItem failures don't reject the whole batch
(call store.clear(), then await Promise.allSettled(photos.map(p =>
store.setItem(p.metaEnvelopeId, p))) and optionally inspect results to log or
ignore rejected entries) while keeping the outer try/catch non-fatal.
- Around line 43-48: The current getCachedPhotosForEname function loads
everything via getAllCachedPhotos then filters in memory; change it to iterate
the underlying store (e.g., localforage.iterate) and collect only entries whose
p.ename === ename, returning early if you can satisfy any expected limit, so you
avoid O(n) memory allocation; alternatively create a separate localforage
instance or IndexedDB index keyed by ename and query that store instead of
calling getAllCachedPhotos — update the implementation of
getCachedPhotosForEname to use localforage.iterate (or the new indexed store)
and remove the getAllCachedPhotos-dependent filtering.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 155-204: Replace the two manual fetch calls and header
construction in loadBindingDocuments with the existing vaultGqlRequest helper:
stop building headers and Authorization using PUBLIC_EID_WALLET_TOKEN and
instead call vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for
the "id_document" and "self" queries (reuse the existing typedQuery
string/variables shape), then feed the returned Response/JSON into the existing
parseEdges logic (or adapt parseEdges to accept the vaultGqlRequest result).
This keeps auth/error handling consistent and removes the inline fetch/header
duplication (references: vaultGqlRequest, typedQuery, parseEdges,
idDocEntry/selfEntry).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f547d467-77a0-42d7-9d93-b0263d25b9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 972f1f6 and 4fe6241.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

@coodos
coodos merged commit f31d097 into mainJun 5, 2026
4 checks passed
@coodos
coodos deleted the fix/loadingIssue branch June 5, 2026 09:31
@coderabbitaicoderabbitaiBot mentioned this pull request Jul 15, 2026
6 tasks
Sign up for freeto 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.

eID Wallet: Improve UX for blocking loads on login

2 participants

@Sahil2004@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Fix/loading issue - #1007

Merged
coodos merged 14 commits into
mainfrom
fix/loadingIssue
Jun 5, 2026
Merged

Fix/loading issue#1007
coodos merged 14 commits into
mainfrom
fix/loadingIssue

Conversation

@Sahil2004

@Sahil2004Sahil2004 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixed the loading issue. Removed all the unecessary loading of huge data and then stripping it at frontend in favour of getting only that data which is required at frontend. Implemented skeletons for places where data is loading asynchronously. Implemented caching using LocalForge. Fixed the ordering issue when creating, editing or deleting anything. Fixed inter-user cache data leak. Brought down the image loading from several minutes to around 30s.

Issue Number

Closes#1006

Type of change

  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Photos are now cached locally for faster loading on subsequent visits.
  • Improvements

    • Enhanced loading states with visual placeholders for smoother experience.
    • Optimized personal data and photograph synchronization for better performance.
    • Improved deletion handling to prevent deleted photos from reappearing.
    • Cache automatically clears on logout for privacy.

…nd changed the query on the home page to get the count only instead of all the images in the personal documents as not required.
…red. Request data that you need instead of getting all data and then throwing it away.
@Sahil2004Sahil2004 self-assigned this Jun 4, 2026
@coderabbitai

coderabbitaiBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0790e52e-f65c-4ab5-acfb-eb3cfe9c58ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6241 and d83144b.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

📝 Walkthrough

Walkthrough

Adds IndexedDB photo caching, splits personal binding loaders (parameters, security question, photographs) with a skip-photo-blobs mode, narrows binding queries, and updates main/personal routes to hydrate progressively with skeleton UI and non-blocking parallel loads.

Changes

Progressive Photo Loading and Skeleton UI for eID Wallet

Layer / File(s)Summary
Photo cache infrastructure with localforage
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src/lib/utils/photoCache.ts
New localforage dependency; photoCache.ts provides CachedPhoto and non-fatal CRUD/filter/replace helpers persisted by metaEnvelopeId.
Split personal data loaders with skipPhotoBlobs option
infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
Adds PERSONAL_PHOTO_IDS_QUERY, three dedicated loaders (loadPersonalParameters, loadPersonalSecurityQuestion, loadPersonalPhotographs), updates loadPersonalBindings to accept { skipPhotoBlobs }, and adds a count-only _loadPersonalBindingsCountOnly using Promise.allSettled.
Binding document query optimization
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Adds BINDING_DOCS_BY_TYPE_QUERY, extends fetchNameFromVault with { nameOnly } mode to fetch only id_document/self, and narrows SOCIAL_BINDING_DOCS_QUERY to type: social_connection.
Main page parallel hydration and skeleton loading
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Adds bindingDocsLoading, refactors loadBindingDocuments to fetch id/self concurrently, deduplicates loadPersonalIntoStore() with an in-flight promise, runs initial hydration in parallel with Promise.allSettled(), and renders a binding-docs loading skeleton.
Personal page cached hydration and progressive loading
infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
Hydrates from cached photos first, then fetches parameters/security question/photos in parallel; introduces locallyDeletedIds, preserves local pending uploads, replaces store with server-visible + pending photos, syncs cache on save/delete, and renders per-photo skeletons and section placeholders while loading.
IdentityCard reactive classes
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Computes baseClasses as a reactive derived value so container classes track variant changes.
Cache invalidation on logout
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Awaits clearAllCachedPhotos() before globalState.reset() during logout to avoid stale cached images.

Sequence Diagram(s)

sequenceDiagram
participant User
participant MainPage
participant PersonalBinding
participant SocialBinding
participant PhotoCache
participant VaultAPI
User->>MainPage: Navigate to /main (login)
MainPage->>MainPage: Init bindingDocsLoading = true, show skeleton
par Parallel hydration
MainPage->>SocialBinding: fetchNameFromVault(nameOnly=true)
MainPage->>PersonalBinding: loadPersonalBindings(skipPhotoBlobs=true)
end
PersonalBinding->>PhotoCache: getCachedPhotosForEname() (immediate hydration)
PhotoCache-->>PersonalBinding: Cached photos
PersonalBinding->>MainPage: Update personal store with cached data
par Parallel network loads
SocialBinding->>VaultAPI: Fetch id_document, self binding docs
PersonalBinding->>VaultAPI: Fetch parameters, security_question, photograph ids
end
VaultAPI-->>SocialBinding: binding edges
VaultAPI-->>PersonalBinding: personal edges
PersonalBinding->>PhotoCache: Write server photos to cache
MainPage->>MainPage: bindingDocsLoading = false, render content
User->>MainPage: View personal/binding data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

evault-refactor

Suggested reviewers

  • coodos
  • sosweetham

🐰 A rabbit's ode to swifter logins:
Where once the user waited, blank and cold,
Now skeletons dance as photos load,
Cached blobs spring forth from IndexedDB's hold,
And binding docs in parallel are sowed. ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check nameStatusExplanationResolution
Title check❓ InconclusiveThe title 'Fix/loading issue' is vague and generic, using non-descriptive terms that don't convey specific information about what was actually fixed.Use a more descriptive title that highlights the main change, such as 'Implement non-blocking login with skeleton screens and LocalForage caching' or 'Optimize personal data loading with progressive UI and image caching'.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description check✅ PassedThe description covers all required template sections including issue number, type of change, testing approach, and completed checklist items with meaningful context about the changes made.
Linked Issues check✅ PassedThe PR successfully addresses issue #1006's acceptance criteria: eliminates blocking loads via parallel loading, implements skeleton screens for visual feedback, maintains data integrity, and reduces image loading time from minutes to ~30s.
Out of Scope Changes check✅ PassedAll changes align with the stated objectives: LocalForage caching implementation, skeleton UI additions, parallel data loading, cache invalidation on logout, and metadata-only photo queries are all directly aimed at resolving the blocking login load issue.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loadingIssue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sahil2004
Sahil2004 marked this pull request as ready for review June 5, 2026 08:20
@Sahil2004
Sahil2004 requested a review from coodos as a code ownerJune 5, 2026 08:20

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/photoCache.ts (2)

82-93: ⚡ Quick win

Partial-write risk in atomic cache replacement.

replaceAllCachedPhotos clears the cache and then writes all photos via Promise.all. If any single setItem call fails mid-flight, the outer catch aborts the entire operation, leaving the cache cleared but only partially refilled.

While the cache is non-fatal and the network remains the source of truth, a partial write means users see incomplete cached data (longer skeleton screens) on the next visit until the network fetch completes.

🛡️ Suggested improvement

Use Promise.allSettled instead of Promise.all so individual write failures don't abort the batch:

 export async function replaceAllCachedPhotos(
photos: CachedPhoto[],
): Promise<void> {
try {
await store.clear();
- await Promise.all(+ const results = await Promise.allSettled(
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
);
+ const failures = results.filter((r) => r.status === "rejected");+ if (failures.length > 0) {+ console.warn(`[photoCache] ${failures.length} photo(s) failed to cache`);+ }
} catch {
// non-fatal
}
}

This allows partial success—most photos are cached even if a few writes fail.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 82 - 93,
The current replaceAllCachedPhotos implementation clears the store then uses
Promise.all for store.setItem calls which can leave the cache partially refilled
if one write fails; change the writes to use Promise.allSettled so individual
setItem failures don't reject the whole batch (call store.clear(), then await
Promise.allSettled(photos.map(p => store.setItem(p.metaEnvelopeId, p))) and
optionally inspect results to log or ignore rejected entries) while keeping the
outer try/catch non-fatal.

43-48: ⚡ Quick win

Consider optimizing photo retrieval by ename.

getCachedPhotosForEname loads all cached photos via getAllCachedPhotos(), then filters by ename in memory. If the cache accumulates many photos across multiple users, this becomes inefficient.

Consider using localforage's iterate directly with early-exit or adding an IndexedDB index on ename for O(log n) lookup instead of O(n) scan.

⚡ Suggested optimization
 export async function getCachedPhotosForEname(
ename: string,
): Promise<CachedPhoto[]> {
- const all = await getAllCachedPhotos();- return all.filter((p) => p.ename === ename);+ try {+ const photos: CachedPhoto[] = [];+ await store.iterate<CachedPhoto, void>((value) => {+ if (value.ename === ename) {+ photos.push(value);+ }+ });+ return photos;+ } catch {+ return [];+ }
}

This avoids loading unrelated photos into memory. For a more robust solution, consider creating a localforage instance with an IndexedDB index on ename.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 43 - 48,
The current getCachedPhotosForEname function loads everything via
getAllCachedPhotos then filters in memory; change it to iterate the underlying
store (e.g., localforage.iterate) and collect only entries whose p.ename ===
ename, returning early if you can satisfy any expected limit, so you avoid O(n)
memory allocation; alternatively create a separate localforage instance or
IndexedDB index keyed by ename and query that store instead of calling
getAllCachedPhotos — update the implementation of getCachedPhotosForEname to use
localforage.iterate (or the new indexed store) and remove the
getAllCachedPhotos-dependent filtering.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

155-204: ⚡ Quick win

Consider using the vaultGqlRequest helper for consistency.

loadBindingDocuments constructs inline fetch() calls with manual header building and query strings (lines 155-176). The codebase already provides a vaultGqlRequest helper (imported from $lib/utils at line 42) that handles headers, error parsing, and auth tokens.

Using the helper would reduce duplication and keep the GraphQL request pattern consistent across the eID wallet module.

♻️ Suggested refactor

Replace the inline fetch with vaultGqlRequest:

+ import { vaultGqlRequest } from "$lib/utils/socialBinding";++ const BINDING_DOCS_BY_TYPE_QUERY = `+ query($type: BindingDocumentType!) {+ bindingDocuments(type: $type, first: 50) {+ edges { node { id parsed } }+ }+ }+ `;+
async function loadBindingDocuments(): Promise<void> {
if (!globalState) return;
const vault = await globalState.vaultController.vault;
if (!vault?.uri || !vault?.ename) return;
- const enameHeader = vault.ename.startsWith("@")- ? vault.ename- : `@${vault.ename}`;
const gqlUrl = new URL("/graphql", vault.uri).toString();
-- const headers: Record<string, string> = {- "Content-Type": "application/json",- "X-ENAME": enameHeader,- ...(PUBLIC_EID_WALLET_TOKEN- ? { Authorization: `Bearer ${PUBLIC_EID_WALLET_TOKEN}` }- : {}),- };- const typedQuery = (type: string) =>- JSON.stringify({- query: `query($type: BindingDocumentType!) {- bindingDocuments(type: $type, first: 50) {- edges { node { id parsed } }- }- }`,- variables: { type },- });+ const ownerEname = vault.ename;
try {
const [idDocRes, selfRes] = await Promise.all([
- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("id_document") }),- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("self") }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "id_document" }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "self" }),
]);
- const parseEdges = async (res: Response) => {- const json = await res.json();- return (json?.data?.bindingDocuments?.edges ?? []) as {- node: { id: string; parsed: ParsedBindingDoc | null };- }[];- };-- const [idDocEdges, selfEdges] = await Promise.all([- parseEdges(idDocRes),- parseEdges(selfRes),- ]);+ const idDocEdges = idDocRes.bindingDocuments?.edges ?? [];+ const selfEdges = selfRes.bindingDocuments?.edges ?? [];
const idDocEntry = idDocEdges.find((e) => e.node.parsed?.type === "id_document");
legalId = idDocEntry?.node.parsed ? toLegalIdDoc(idDocEntry.node.parsed) : null;
🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
155 - 204, Replace the two manual fetch calls and header construction in
loadBindingDocuments with the existing vaultGqlRequest helper: stop building
headers and Authorization using PUBLIC_EID_WALLET_TOKEN and instead call
vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for the
"id_document" and "self" queries (reuse the existing typedQuery string/variables
shape), then feed the returned Response/JSON into the existing parseEdges logic
(or adapt parseEdges to accept the vaultGqlRequest result). This keeps
auth/error handling consistent and removes the inline fetch/header duplication
(references: vaultGqlRequest, typedQuery, parseEdges, idDocEntry/selfEntry).
🤖 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.
Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts`:
- Around line 82-93: The current replaceAllCachedPhotos implementation clears
the store then uses Promise.all for store.setItem calls which can leave the
cache partially refilled if one write fails; change the writes to use
Promise.allSettled so individual setItem failures don't reject the whole batch
(call store.clear(), then await Promise.allSettled(photos.map(p =>
store.setItem(p.metaEnvelopeId, p))) and optionally inspect results to log or
ignore rejected entries) while keeping the outer try/catch non-fatal.
- Around line 43-48: The current getCachedPhotosForEname function loads
everything via getAllCachedPhotos then filters in memory; change it to iterate
the underlying store (e.g., localforage.iterate) and collect only entries whose
p.ename === ename, returning early if you can satisfy any expected limit, so you
avoid O(n) memory allocation; alternatively create a separate localforage
instance or IndexedDB index keyed by ename and query that store instead of
calling getAllCachedPhotos — update the implementation of
getCachedPhotosForEname to use localforage.iterate (or the new indexed store)
and remove the getAllCachedPhotos-dependent filtering.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 155-204: Replace the two manual fetch calls and header
construction in loadBindingDocuments with the existing vaultGqlRequest helper:
stop building headers and Authorization using PUBLIC_EID_WALLET_TOKEN and
instead call vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for
the "id_document" and "self" queries (reuse the existing typedQuery
string/variables shape), then feed the returned Response/JSON into the existing
parseEdges logic (or adapt parseEdges to accept the vaultGqlRequest result).
This keeps auth/error handling consistent and removes the inline fetch/header
duplication (references: vaultGqlRequest, typedQuery, parseEdges,
idDocEntry/selfEntry).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f547d467-77a0-42d7-9d93-b0263d25b9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 972f1f6 and 4fe6241.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

@coodos
coodos merged commit f31d097 into mainJun 5, 2026
4 checks passed
@coodos
coodos deleted the fix/loadingIssue branch June 5, 2026 09:31
@coderabbitaicoderabbitaiBot mentioned this pull request Jul 15, 2026
6 tasks
Sign up for freeto 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.

eID Wallet: Improve UX for blocking loads on login

2 participants

@Sahil2004@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix/loading issue - #1007

Merged
coodos merged 14 commits into
mainfrom
fix/loadingIssue
Jun 5, 2026
Merged

Fix/loading issue#1007
coodos merged 14 commits into
mainfrom
fix/loadingIssue

Conversation

@Sahil2004

@Sahil2004Sahil2004 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixed the loading issue. Removed all the unecessary loading of huge data and then stripping it at frontend in favour of getting only that data which is required at frontend. Implemented skeletons for places where data is loading asynchronously. Implemented caching using LocalForge. Fixed the ordering issue when creating, editing or deleting anything. Fixed inter-user cache data leak. Brought down the image loading from several minutes to around 30s.

Issue Number

Closes#1006

Type of change

  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Photos are now cached locally for faster loading on subsequent visits.
  • Improvements

    • Enhanced loading states with visual placeholders for smoother experience.
    • Optimized personal data and photograph synchronization for better performance.
    • Improved deletion handling to prevent deleted photos from reappearing.
    • Cache automatically clears on logout for privacy.

…nd changed the query on the home page to get the count only instead of all the images in the personal documents as not required.
…red. Request data that you need instead of getting all data and then throwing it away.
@Sahil2004Sahil2004 self-assigned this Jun 4, 2026
@coderabbitai

coderabbitaiBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0790e52e-f65c-4ab5-acfb-eb3cfe9c58ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6241 and d83144b.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

📝 Walkthrough

Walkthrough

Adds IndexedDB photo caching, splits personal binding loaders (parameters, security question, photographs) with a skip-photo-blobs mode, narrows binding queries, and updates main/personal routes to hydrate progressively with skeleton UI and non-blocking parallel loads.

Changes

Progressive Photo Loading and Skeleton UI for eID Wallet

Layer / File(s)Summary
Photo cache infrastructure with localforage
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src/lib/utils/photoCache.ts
New localforage dependency; photoCache.ts provides CachedPhoto and non-fatal CRUD/filter/replace helpers persisted by metaEnvelopeId.
Split personal data loaders with skipPhotoBlobs option
infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
Adds PERSONAL_PHOTO_IDS_QUERY, three dedicated loaders (loadPersonalParameters, loadPersonalSecurityQuestion, loadPersonalPhotographs), updates loadPersonalBindings to accept { skipPhotoBlobs }, and adds a count-only _loadPersonalBindingsCountOnly using Promise.allSettled.
Binding document query optimization
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Adds BINDING_DOCS_BY_TYPE_QUERY, extends fetchNameFromVault with { nameOnly } mode to fetch only id_document/self, and narrows SOCIAL_BINDING_DOCS_QUERY to type: social_connection.
Main page parallel hydration and skeleton loading
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Adds bindingDocsLoading, refactors loadBindingDocuments to fetch id/self concurrently, deduplicates loadPersonalIntoStore() with an in-flight promise, runs initial hydration in parallel with Promise.allSettled(), and renders a binding-docs loading skeleton.
Personal page cached hydration and progressive loading
infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
Hydrates from cached photos first, then fetches parameters/security question/photos in parallel; introduces locallyDeletedIds, preserves local pending uploads, replaces store with server-visible + pending photos, syncs cache on save/delete, and renders per-photo skeletons and section placeholders while loading.
IdentityCard reactive classes
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Computes baseClasses as a reactive derived value so container classes track variant changes.
Cache invalidation on logout
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Awaits clearAllCachedPhotos() before globalState.reset() during logout to avoid stale cached images.

Sequence Diagram(s)

sequenceDiagram
participant User
participant MainPage
participant PersonalBinding
participant SocialBinding
participant PhotoCache
participant VaultAPI
User->>MainPage: Navigate to /main (login)
MainPage->>MainPage: Init bindingDocsLoading = true, show skeleton
par Parallel hydration
MainPage->>SocialBinding: fetchNameFromVault(nameOnly=true)
MainPage->>PersonalBinding: loadPersonalBindings(skipPhotoBlobs=true)
end
PersonalBinding->>PhotoCache: getCachedPhotosForEname() (immediate hydration)
PhotoCache-->>PersonalBinding: Cached photos
PersonalBinding->>MainPage: Update personal store with cached data
par Parallel network loads
SocialBinding->>VaultAPI: Fetch id_document, self binding docs
PersonalBinding->>VaultAPI: Fetch parameters, security_question, photograph ids
end
VaultAPI-->>SocialBinding: binding edges
VaultAPI-->>PersonalBinding: personal edges
PersonalBinding->>PhotoCache: Write server photos to cache
MainPage->>MainPage: bindingDocsLoading = false, render content
User->>MainPage: View personal/binding data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

evault-refactor

Suggested reviewers

  • coodos
  • sosweetham

🐰 A rabbit's ode to swifter logins:
Where once the user waited, blank and cold,
Now skeletons dance as photos load,
Cached blobs spring forth from IndexedDB's hold,
And binding docs in parallel are sowed. ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check nameStatusExplanationResolution
Title check❓ InconclusiveThe title 'Fix/loading issue' is vague and generic, using non-descriptive terms that don't convey specific information about what was actually fixed.Use a more descriptive title that highlights the main change, such as 'Implement non-blocking login with skeleton screens and LocalForage caching' or 'Optimize personal data loading with progressive UI and image caching'.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description check✅ PassedThe description covers all required template sections including issue number, type of change, testing approach, and completed checklist items with meaningful context about the changes made.
Linked Issues check✅ PassedThe PR successfully addresses issue #1006's acceptance criteria: eliminates blocking loads via parallel loading, implements skeleton screens for visual feedback, maintains data integrity, and reduces image loading time from minutes to ~30s.
Out of Scope Changes check✅ PassedAll changes align with the stated objectives: LocalForage caching implementation, skeleton UI additions, parallel data loading, cache invalidation on logout, and metadata-only photo queries are all directly aimed at resolving the blocking login load issue.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loadingIssue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sahil2004
Sahil2004 marked this pull request as ready for review June 5, 2026 08:20
@Sahil2004
Sahil2004 requested a review from coodos as a code ownerJune 5, 2026 08:20

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/photoCache.ts (2)

82-93: ⚡ Quick win

Partial-write risk in atomic cache replacement.

replaceAllCachedPhotos clears the cache and then writes all photos via Promise.all. If any single setItem call fails mid-flight, the outer catch aborts the entire operation, leaving the cache cleared but only partially refilled.

While the cache is non-fatal and the network remains the source of truth, a partial write means users see incomplete cached data (longer skeleton screens) on the next visit until the network fetch completes.

🛡️ Suggested improvement

Use Promise.allSettled instead of Promise.all so individual write failures don't abort the batch:

 export async function replaceAllCachedPhotos(
photos: CachedPhoto[],
): Promise<void> {
try {
await store.clear();
- await Promise.all(+ const results = await Promise.allSettled(
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
);
+ const failures = results.filter((r) => r.status === "rejected");+ if (failures.length > 0) {+ console.warn(`[photoCache] ${failures.length} photo(s) failed to cache`);+ }
} catch {
// non-fatal
}
}

This allows partial success—most photos are cached even if a few writes fail.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 82 - 93,
The current replaceAllCachedPhotos implementation clears the store then uses
Promise.all for store.setItem calls which can leave the cache partially refilled
if one write fails; change the writes to use Promise.allSettled so individual
setItem failures don't reject the whole batch (call store.clear(), then await
Promise.allSettled(photos.map(p => store.setItem(p.metaEnvelopeId, p))) and
optionally inspect results to log or ignore rejected entries) while keeping the
outer try/catch non-fatal.

43-48: ⚡ Quick win

Consider optimizing photo retrieval by ename.

getCachedPhotosForEname loads all cached photos via getAllCachedPhotos(), then filters by ename in memory. If the cache accumulates many photos across multiple users, this becomes inefficient.

Consider using localforage's iterate directly with early-exit or adding an IndexedDB index on ename for O(log n) lookup instead of O(n) scan.

⚡ Suggested optimization
 export async function getCachedPhotosForEname(
ename: string,
): Promise<CachedPhoto[]> {
- const all = await getAllCachedPhotos();- return all.filter((p) => p.ename === ename);+ try {+ const photos: CachedPhoto[] = [];+ await store.iterate<CachedPhoto, void>((value) => {+ if (value.ename === ename) {+ photos.push(value);+ }+ });+ return photos;+ } catch {+ return [];+ }
}

This avoids loading unrelated photos into memory. For a more robust solution, consider creating a localforage instance with an IndexedDB index on ename.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 43 - 48,
The current getCachedPhotosForEname function loads everything via
getAllCachedPhotos then filters in memory; change it to iterate the underlying
store (e.g., localforage.iterate) and collect only entries whose p.ename ===
ename, returning early if you can satisfy any expected limit, so you avoid O(n)
memory allocation; alternatively create a separate localforage instance or
IndexedDB index keyed by ename and query that store instead of calling
getAllCachedPhotos — update the implementation of getCachedPhotosForEname to use
localforage.iterate (or the new indexed store) and remove the
getAllCachedPhotos-dependent filtering.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

155-204: ⚡ Quick win

Consider using the vaultGqlRequest helper for consistency.

loadBindingDocuments constructs inline fetch() calls with manual header building and query strings (lines 155-176). The codebase already provides a vaultGqlRequest helper (imported from $lib/utils at line 42) that handles headers, error parsing, and auth tokens.

Using the helper would reduce duplication and keep the GraphQL request pattern consistent across the eID wallet module.

♻️ Suggested refactor

Replace the inline fetch with vaultGqlRequest:

+ import { vaultGqlRequest } from "$lib/utils/socialBinding";++ const BINDING_DOCS_BY_TYPE_QUERY = `+ query($type: BindingDocumentType!) {+ bindingDocuments(type: $type, first: 50) {+ edges { node { id parsed } }+ }+ }+ `;+
async function loadBindingDocuments(): Promise<void> {
if (!globalState) return;
const vault = await globalState.vaultController.vault;
if (!vault?.uri || !vault?.ename) return;
- const enameHeader = vault.ename.startsWith("@")- ? vault.ename- : `@${vault.ename}`;
const gqlUrl = new URL("/graphql", vault.uri).toString();
-- const headers: Record<string, string> = {- "Content-Type": "application/json",- "X-ENAME": enameHeader,- ...(PUBLIC_EID_WALLET_TOKEN- ? { Authorization: `Bearer ${PUBLIC_EID_WALLET_TOKEN}` }- : {}),- };- const typedQuery = (type: string) =>- JSON.stringify({- query: `query($type: BindingDocumentType!) {- bindingDocuments(type: $type, first: 50) {- edges { node { id parsed } }- }- }`,- variables: { type },- });+ const ownerEname = vault.ename;
try {
const [idDocRes, selfRes] = await Promise.all([
- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("id_document") }),- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("self") }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "id_document" }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "self" }),
]);
- const parseEdges = async (res: Response) => {- const json = await res.json();- return (json?.data?.bindingDocuments?.edges ?? []) as {- node: { id: string; parsed: ParsedBindingDoc | null };- }[];- };-- const [idDocEdges, selfEdges] = await Promise.all([- parseEdges(idDocRes),- parseEdges(selfRes),- ]);+ const idDocEdges = idDocRes.bindingDocuments?.edges ?? [];+ const selfEdges = selfRes.bindingDocuments?.edges ?? [];
const idDocEntry = idDocEdges.find((e) => e.node.parsed?.type === "id_document");
legalId = idDocEntry?.node.parsed ? toLegalIdDoc(idDocEntry.node.parsed) : null;
🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
155 - 204, Replace the two manual fetch calls and header construction in
loadBindingDocuments with the existing vaultGqlRequest helper: stop building
headers and Authorization using PUBLIC_EID_WALLET_TOKEN and instead call
vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for the
"id_document" and "self" queries (reuse the existing typedQuery string/variables
shape), then feed the returned Response/JSON into the existing parseEdges logic
(or adapt parseEdges to accept the vaultGqlRequest result). This keeps
auth/error handling consistent and removes the inline fetch/header duplication
(references: vaultGqlRequest, typedQuery, parseEdges, idDocEntry/selfEntry).
🤖 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.
Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts`:
- Around line 82-93: The current replaceAllCachedPhotos implementation clears
the store then uses Promise.all for store.setItem calls which can leave the
cache partially refilled if one write fails; change the writes to use
Promise.allSettled so individual setItem failures don't reject the whole batch
(call store.clear(), then await Promise.allSettled(photos.map(p =>
store.setItem(p.metaEnvelopeId, p))) and optionally inspect results to log or
ignore rejected entries) while keeping the outer try/catch non-fatal.
- Around line 43-48: The current getCachedPhotosForEname function loads
everything via getAllCachedPhotos then filters in memory; change it to iterate
the underlying store (e.g., localforage.iterate) and collect only entries whose
p.ename === ename, returning early if you can satisfy any expected limit, so you
avoid O(n) memory allocation; alternatively create a separate localforage
instance or IndexedDB index keyed by ename and query that store instead of
calling getAllCachedPhotos — update the implementation of
getCachedPhotosForEname to use localforage.iterate (or the new indexed store)
and remove the getAllCachedPhotos-dependent filtering.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 155-204: Replace the two manual fetch calls and header
construction in loadBindingDocuments with the existing vaultGqlRequest helper:
stop building headers and Authorization using PUBLIC_EID_WALLET_TOKEN and
instead call vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for
the "id_document" and "self" queries (reuse the existing typedQuery
string/variables shape), then feed the returned Response/JSON into the existing
parseEdges logic (or adapt parseEdges to accept the vaultGqlRequest result).
This keeps auth/error handling consistent and removes the inline fetch/header
duplication (references: vaultGqlRequest, typedQuery, parseEdges,
idDocEntry/selfEntry).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f547d467-77a0-42d7-9d93-b0263d25b9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 972f1f6 and 4fe6241.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

@coodos
coodos merged commit f31d097 into mainJun 5, 2026
4 checks passed
@coodos
coodos deleted the fix/loadingIssue branch June 5, 2026 09:31
@coderabbitaicoderabbitaiBot mentioned this pull request Jul 15, 2026
6 tasks
Sign up for freeto 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.

eID Wallet: Improve UX for blocking loads on login

2 participants

@Sahil2004@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Fix/loading issue - #1007

Merged
coodos merged 14 commits into
mainfrom
fix/loadingIssue
Jun 5, 2026
Merged

Fix/loading issue#1007
coodos merged 14 commits into
mainfrom
fix/loadingIssue

Conversation

@Sahil2004

@Sahil2004Sahil2004 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixed the loading issue. Removed all the unecessary loading of huge data and then stripping it at frontend in favour of getting only that data which is required at frontend. Implemented skeletons for places where data is loading asynchronously. Implemented caching using LocalForge. Fixed the ordering issue when creating, editing or deleting anything. Fixed inter-user cache data leak. Brought down the image loading from several minutes to around 30s.

Issue Number

Closes#1006

Type of change

  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Photos are now cached locally for faster loading on subsequent visits.
  • Improvements

    • Enhanced loading states with visual placeholders for smoother experience.
    • Optimized personal data and photograph synchronization for better performance.
    • Improved deletion handling to prevent deleted photos from reappearing.
    • Cache automatically clears on logout for privacy.

…nd changed the query on the home page to get the count only instead of all the images in the personal documents as not required.
…red. Request data that you need instead of getting all data and then throwing it away.
@Sahil2004Sahil2004 self-assigned this Jun 4, 2026
@coderabbitai

coderabbitaiBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0790e52e-f65c-4ab5-acfb-eb3cfe9c58ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6241 and d83144b.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

📝 Walkthrough

Walkthrough

Adds IndexedDB photo caching, splits personal binding loaders (parameters, security question, photographs) with a skip-photo-blobs mode, narrows binding queries, and updates main/personal routes to hydrate progressively with skeleton UI and non-blocking parallel loads.

Changes

Progressive Photo Loading and Skeleton UI for eID Wallet

Layer / File(s)Summary
Photo cache infrastructure with localforage
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src/lib/utils/photoCache.ts
New localforage dependency; photoCache.ts provides CachedPhoto and non-fatal CRUD/filter/replace helpers persisted by metaEnvelopeId.
Split personal data loaders with skipPhotoBlobs option
infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
Adds PERSONAL_PHOTO_IDS_QUERY, three dedicated loaders (loadPersonalParameters, loadPersonalSecurityQuestion, loadPersonalPhotographs), updates loadPersonalBindings to accept { skipPhotoBlobs }, and adds a count-only _loadPersonalBindingsCountOnly using Promise.allSettled.
Binding document query optimization
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Adds BINDING_DOCS_BY_TYPE_QUERY, extends fetchNameFromVault with { nameOnly } mode to fetch only id_document/self, and narrows SOCIAL_BINDING_DOCS_QUERY to type: social_connection.
Main page parallel hydration and skeleton loading
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Adds bindingDocsLoading, refactors loadBindingDocuments to fetch id/self concurrently, deduplicates loadPersonalIntoStore() with an in-flight promise, runs initial hydration in parallel with Promise.allSettled(), and renders a binding-docs loading skeleton.
Personal page cached hydration and progressive loading
infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
Hydrates from cached photos first, then fetches parameters/security question/photos in parallel; introduces locallyDeletedIds, preserves local pending uploads, replaces store with server-visible + pending photos, syncs cache on save/delete, and renders per-photo skeletons and section placeholders while loading.
IdentityCard reactive classes
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Computes baseClasses as a reactive derived value so container classes track variant changes.
Cache invalidation on logout
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Awaits clearAllCachedPhotos() before globalState.reset() during logout to avoid stale cached images.

Sequence Diagram(s)

sequenceDiagram
participant User
participant MainPage
participant PersonalBinding
participant SocialBinding
participant PhotoCache
participant VaultAPI
User->>MainPage: Navigate to /main (login)
MainPage->>MainPage: Init bindingDocsLoading = true, show skeleton
par Parallel hydration
MainPage->>SocialBinding: fetchNameFromVault(nameOnly=true)
MainPage->>PersonalBinding: loadPersonalBindings(skipPhotoBlobs=true)
end
PersonalBinding->>PhotoCache: getCachedPhotosForEname() (immediate hydration)
PhotoCache-->>PersonalBinding: Cached photos
PersonalBinding->>MainPage: Update personal store with cached data
par Parallel network loads
SocialBinding->>VaultAPI: Fetch id_document, self binding docs
PersonalBinding->>VaultAPI: Fetch parameters, security_question, photograph ids
end
VaultAPI-->>SocialBinding: binding edges
VaultAPI-->>PersonalBinding: personal edges
PersonalBinding->>PhotoCache: Write server photos to cache
MainPage->>MainPage: bindingDocsLoading = false, render content
User->>MainPage: View personal/binding data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

evault-refactor

Suggested reviewers

  • coodos
  • sosweetham

🐰 A rabbit's ode to swifter logins:
Where once the user waited, blank and cold,
Now skeletons dance as photos load,
Cached blobs spring forth from IndexedDB's hold,
And binding docs in parallel are sowed. ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check nameStatusExplanationResolution
Title check❓ InconclusiveThe title 'Fix/loading issue' is vague and generic, using non-descriptive terms that don't convey specific information about what was actually fixed.Use a more descriptive title that highlights the main change, such as 'Implement non-blocking login with skeleton screens and LocalForage caching' or 'Optimize personal data loading with progressive UI and image caching'.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description check✅ PassedThe description covers all required template sections including issue number, type of change, testing approach, and completed checklist items with meaningful context about the changes made.
Linked Issues check✅ PassedThe PR successfully addresses issue #1006's acceptance criteria: eliminates blocking loads via parallel loading, implements skeleton screens for visual feedback, maintains data integrity, and reduces image loading time from minutes to ~30s.
Out of Scope Changes check✅ PassedAll changes align with the stated objectives: LocalForage caching implementation, skeleton UI additions, parallel data loading, cache invalidation on logout, and metadata-only photo queries are all directly aimed at resolving the blocking login load issue.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loadingIssue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sahil2004
Sahil2004 marked this pull request as ready for review June 5, 2026 08:20
@Sahil2004
Sahil2004 requested a review from coodos as a code ownerJune 5, 2026 08:20

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/photoCache.ts (2)

82-93: ⚡ Quick win

Partial-write risk in atomic cache replacement.

replaceAllCachedPhotos clears the cache and then writes all photos via Promise.all. If any single setItem call fails mid-flight, the outer catch aborts the entire operation, leaving the cache cleared but only partially refilled.

While the cache is non-fatal and the network remains the source of truth, a partial write means users see incomplete cached data (longer skeleton screens) on the next visit until the network fetch completes.

🛡️ Suggested improvement

Use Promise.allSettled instead of Promise.all so individual write failures don't abort the batch:

 export async function replaceAllCachedPhotos(
photos: CachedPhoto[],
): Promise<void> {
try {
await store.clear();
- await Promise.all(+ const results = await Promise.allSettled(
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
);
+ const failures = results.filter((r) => r.status === "rejected");+ if (failures.length > 0) {+ console.warn(`[photoCache] ${failures.length} photo(s) failed to cache`);+ }
} catch {
// non-fatal
}
}

This allows partial success—most photos are cached even if a few writes fail.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 82 - 93,
The current replaceAllCachedPhotos implementation clears the store then uses
Promise.all for store.setItem calls which can leave the cache partially refilled
if one write fails; change the writes to use Promise.allSettled so individual
setItem failures don't reject the whole batch (call store.clear(), then await
Promise.allSettled(photos.map(p => store.setItem(p.metaEnvelopeId, p))) and
optionally inspect results to log or ignore rejected entries) while keeping the
outer try/catch non-fatal.

43-48: ⚡ Quick win

Consider optimizing photo retrieval by ename.

getCachedPhotosForEname loads all cached photos via getAllCachedPhotos(), then filters by ename in memory. If the cache accumulates many photos across multiple users, this becomes inefficient.

Consider using localforage's iterate directly with early-exit or adding an IndexedDB index on ename for O(log n) lookup instead of O(n) scan.

⚡ Suggested optimization
 export async function getCachedPhotosForEname(
ename: string,
): Promise<CachedPhoto[]> {
- const all = await getAllCachedPhotos();- return all.filter((p) => p.ename === ename);+ try {+ const photos: CachedPhoto[] = [];+ await store.iterate<CachedPhoto, void>((value) => {+ if (value.ename === ename) {+ photos.push(value);+ }+ });+ return photos;+ } catch {+ return [];+ }
}

This avoids loading unrelated photos into memory. For a more robust solution, consider creating a localforage instance with an IndexedDB index on ename.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 43 - 48,
The current getCachedPhotosForEname function loads everything via
getAllCachedPhotos then filters in memory; change it to iterate the underlying
store (e.g., localforage.iterate) and collect only entries whose p.ename ===
ename, returning early if you can satisfy any expected limit, so you avoid O(n)
memory allocation; alternatively create a separate localforage instance or
IndexedDB index keyed by ename and query that store instead of calling
getAllCachedPhotos — update the implementation of getCachedPhotosForEname to use
localforage.iterate (or the new indexed store) and remove the
getAllCachedPhotos-dependent filtering.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

155-204: ⚡ Quick win

Consider using the vaultGqlRequest helper for consistency.

loadBindingDocuments constructs inline fetch() calls with manual header building and query strings (lines 155-176). The codebase already provides a vaultGqlRequest helper (imported from $lib/utils at line 42) that handles headers, error parsing, and auth tokens.

Using the helper would reduce duplication and keep the GraphQL request pattern consistent across the eID wallet module.

♻️ Suggested refactor

Replace the inline fetch with vaultGqlRequest:

+ import { vaultGqlRequest } from "$lib/utils/socialBinding";++ const BINDING_DOCS_BY_TYPE_QUERY = `+ query($type: BindingDocumentType!) {+ bindingDocuments(type: $type, first: 50) {+ edges { node { id parsed } }+ }+ }+ `;+
async function loadBindingDocuments(): Promise<void> {
if (!globalState) return;
const vault = await globalState.vaultController.vault;
if (!vault?.uri || !vault?.ename) return;
- const enameHeader = vault.ename.startsWith("@")- ? vault.ename- : `@${vault.ename}`;
const gqlUrl = new URL("/graphql", vault.uri).toString();
-- const headers: Record<string, string> = {- "Content-Type": "application/json",- "X-ENAME": enameHeader,- ...(PUBLIC_EID_WALLET_TOKEN- ? { Authorization: `Bearer ${PUBLIC_EID_WALLET_TOKEN}` }- : {}),- };- const typedQuery = (type: string) =>- JSON.stringify({- query: `query($type: BindingDocumentType!) {- bindingDocuments(type: $type, first: 50) {- edges { node { id parsed } }- }- }`,- variables: { type },- });+ const ownerEname = vault.ename;
try {
const [idDocRes, selfRes] = await Promise.all([
- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("id_document") }),- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("self") }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "id_document" }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "self" }),
]);
- const parseEdges = async (res: Response) => {- const json = await res.json();- return (json?.data?.bindingDocuments?.edges ?? []) as {- node: { id: string; parsed: ParsedBindingDoc | null };- }[];- };-- const [idDocEdges, selfEdges] = await Promise.all([- parseEdges(idDocRes),- parseEdges(selfRes),- ]);+ const idDocEdges = idDocRes.bindingDocuments?.edges ?? [];+ const selfEdges = selfRes.bindingDocuments?.edges ?? [];
const idDocEntry = idDocEdges.find((e) => e.node.parsed?.type === "id_document");
legalId = idDocEntry?.node.parsed ? toLegalIdDoc(idDocEntry.node.parsed) : null;
🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
155 - 204, Replace the two manual fetch calls and header construction in
loadBindingDocuments with the existing vaultGqlRequest helper: stop building
headers and Authorization using PUBLIC_EID_WALLET_TOKEN and instead call
vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for the
"id_document" and "self" queries (reuse the existing typedQuery string/variables
shape), then feed the returned Response/JSON into the existing parseEdges logic
(or adapt parseEdges to accept the vaultGqlRequest result). This keeps
auth/error handling consistent and removes the inline fetch/header duplication
(references: vaultGqlRequest, typedQuery, parseEdges, idDocEntry/selfEntry).
🤖 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.
Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts`:
- Around line 82-93: The current replaceAllCachedPhotos implementation clears
the store then uses Promise.all for store.setItem calls which can leave the
cache partially refilled if one write fails; change the writes to use
Promise.allSettled so individual setItem failures don't reject the whole batch
(call store.clear(), then await Promise.allSettled(photos.map(p =>
store.setItem(p.metaEnvelopeId, p))) and optionally inspect results to log or
ignore rejected entries) while keeping the outer try/catch non-fatal.
- Around line 43-48: The current getCachedPhotosForEname function loads
everything via getAllCachedPhotos then filters in memory; change it to iterate
the underlying store (e.g., localforage.iterate) and collect only entries whose
p.ename === ename, returning early if you can satisfy any expected limit, so you
avoid O(n) memory allocation; alternatively create a separate localforage
instance or IndexedDB index keyed by ename and query that store instead of
calling getAllCachedPhotos — update the implementation of
getCachedPhotosForEname to use localforage.iterate (or the new indexed store)
and remove the getAllCachedPhotos-dependent filtering.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 155-204: Replace the two manual fetch calls and header
construction in loadBindingDocuments with the existing vaultGqlRequest helper:
stop building headers and Authorization using PUBLIC_EID_WALLET_TOKEN and
instead call vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for
the "id_document" and "self" queries (reuse the existing typedQuery
string/variables shape), then feed the returned Response/JSON into the existing
parseEdges logic (or adapt parseEdges to accept the vaultGqlRequest result).
This keeps auth/error handling consistent and removes the inline fetch/header
duplication (references: vaultGqlRequest, typedQuery, parseEdges,
idDocEntry/selfEntry).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f547d467-77a0-42d7-9d93-b0263d25b9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 972f1f6 and 4fe6241.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

@coodos
coodos merged commit f31d097 into mainJun 5, 2026
4 checks passed
@coodos
coodos deleted the fix/loadingIssue branch June 5, 2026 09:31
@coderabbitaicoderabbitaiBot mentioned this pull request Jul 15, 2026
6 tasks
Sign up for freeto 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.

eID Wallet: Improve UX for blocking loads on login

2 participants

@Sahil2004@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Fix/loading issue - #1007

Merged
coodos merged 14 commits into
mainfrom
fix/loadingIssue
Jun 5, 2026
Merged

Fix/loading issue#1007
coodos merged 14 commits into
mainfrom
fix/loadingIssue

Conversation

@Sahil2004

@Sahil2004Sahil2004 commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Description of change

Fixed the loading issue. Removed all the unecessary loading of huge data and then stripping it at frontend in favour of getting only that data which is required at frontend. Implemented skeletons for places where data is loading asynchronously. Implemented caching using LocalForge. Fixed the ordering issue when creating, editing or deleting anything. Fixed inter-user cache data leak. Brought down the image loading from several minutes to around 30s.

Issue Number

Closes#1006

Type of change

  • Update (a change which updates existing functionality)
  • Fix (a change which fixes an issue)

How the change has been tested

Manually.

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

Summary by CodeRabbit

  • New Features

    • Photos are now cached locally for faster loading on subsequent visits.
  • Improvements

    • Enhanced loading states with visual placeholders for smoother experience.
    • Optimized personal data and photograph synchronization for better performance.
    • Improved deletion handling to prevent deleted photos from reappearing.
    • Cache automatically clears on logout for privacy.

…nd changed the query on the home page to get the count only instead of all the images in the personal documents as not required.
…red. Request data that you need instead of getting all data and then throwing it away.
@Sahil2004Sahil2004 self-assigned this Jun 4, 2026
@coderabbitai

coderabbitaiBot commented Jun 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0790e52e-f65c-4ab5-acfb-eb3cfe9c58ee

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe6241 and d83144b.

📒 Files selected for processing (4)
  • infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
✅ Files skipped from review due to trivial changes (1)
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
🚧 Files skipped from review as they are similar to previous changes (2)
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte

📝 Walkthrough

Walkthrough

Adds IndexedDB photo caching, splits personal binding loaders (parameters, security question, photographs) with a skip-photo-blobs mode, narrows binding queries, and updates main/personal routes to hydrate progressively with skeleton UI and non-blocking parallel loads.

Changes

Progressive Photo Loading and Skeleton UI for eID Wallet

Layer / File(s)Summary
Photo cache infrastructure with localforage
infrastructure/eid-wallet/package.json, infrastructure/eid-wallet/src/lib/utils/photoCache.ts
New localforage dependency; photoCache.ts provides CachedPhoto and non-fatal CRUD/filter/replace helpers persisted by metaEnvelopeId.
Split personal data loaders with skipPhotoBlobs option
infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
Adds PERSONAL_PHOTO_IDS_QUERY, three dedicated loaders (loadPersonalParameters, loadPersonalSecurityQuestion, loadPersonalPhotographs), updates loadPersonalBindings to accept { skipPhotoBlobs }, and adds a count-only _loadPersonalBindingsCountOnly using Promise.allSettled.
Binding document query optimization
infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
Adds BINDING_DOCS_BY_TYPE_QUERY, extends fetchNameFromVault with { nameOnly } mode to fetch only id_document/self, and narrows SOCIAL_BINDING_DOCS_QUERY to type: social_connection.
Main page parallel hydration and skeleton loading
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
Adds bindingDocsLoading, refactors loadBindingDocuments to fetch id/self concurrently, deduplicates loadPersonalIntoStore() with an in-flight promise, runs initial hydration in parallel with Promise.allSettled(), and renders a binding-docs loading skeleton.
Personal page cached hydration and progressive loading
infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
Hydrates from cached photos first, then fetches parameters/security question/photos in parallel; introduces locallyDeletedIds, preserves local pending uploads, replaces store with server-visible + pending photos, syncs cache on save/delete, and renders per-photo skeletons and section placeholders while loading.
IdentityCard reactive classes
infrastructure/eid-wallet/src/lib/fragments/IdentityCard/IdentityCard.svelte
Computes baseClasses as a reactive derived value so container classes track variant changes.
Cache invalidation on logout
infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte
Awaits clearAllCachedPhotos() before globalState.reset() during logout to avoid stale cached images.

Sequence Diagram(s)

sequenceDiagram
participant User
participant MainPage
participant PersonalBinding
participant SocialBinding
participant PhotoCache
participant VaultAPI
User->>MainPage: Navigate to /main (login)
MainPage->>MainPage: Init bindingDocsLoading = true, show skeleton
par Parallel hydration
MainPage->>SocialBinding: fetchNameFromVault(nameOnly=true)
MainPage->>PersonalBinding: loadPersonalBindings(skipPhotoBlobs=true)
end
PersonalBinding->>PhotoCache: getCachedPhotosForEname() (immediate hydration)
PhotoCache-->>PersonalBinding: Cached photos
PersonalBinding->>MainPage: Update personal store with cached data
par Parallel network loads
SocialBinding->>VaultAPI: Fetch id_document, self binding docs
PersonalBinding->>VaultAPI: Fetch parameters, security_question, photograph ids
end
VaultAPI-->>SocialBinding: binding edges
VaultAPI-->>PersonalBinding: personal edges
PersonalBinding->>PhotoCache: Write server photos to cache
MainPage->>MainPage: bindingDocsLoading = false, render content
User->>MainPage: View personal/binding data
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

evault-refactor

Suggested reviewers

  • coodos
  • sosweetham

🐰 A rabbit's ode to swifter logins:
Where once the user waited, blank and cold,
Now skeletons dance as photos load,
Cached blobs spring forth from IndexedDB's hold,
And binding docs in parallel are sowed. ✨📸

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check nameStatusExplanationResolution
Title check❓ InconclusiveThe title 'Fix/loading issue' is vague and generic, using non-descriptive terms that don't convey specific information about what was actually fixed.Use a more descriptive title that highlights the main change, such as 'Implement non-blocking login with skeleton screens and LocalForage caching' or 'Optimize personal data loading with progressive UI and image caching'.
✅ Passed checks (4 passed)
Check nameStatusExplanation
Description check✅ PassedThe description covers all required template sections including issue number, type of change, testing approach, and completed checklist items with meaningful context about the changes made.
Linked Issues check✅ PassedThe PR successfully addresses issue #1006's acceptance criteria: eliminates blocking loads via parallel loading, implements skeleton screens for visual feedback, maintains data integrity, and reduces image loading time from minutes to ~30s.
Out of Scope Changes check✅ PassedAll changes align with the stated objectives: LocalForage caching implementation, skeleton UI additions, parallel data loading, cache invalidation on logout, and metadata-only photo queries are all directly aimed at resolving the blocking login load issue.
Docstring Coverage✅ PassedDocstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/loadingIssue

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sahil2004
Sahil2004 marked this pull request as ready for review June 5, 2026 08:20
@Sahil2004
Sahil2004 requested a review from coodos as a code ownerJune 5, 2026 08:20

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
infrastructure/eid-wallet/src/lib/utils/photoCache.ts (2)

82-93: ⚡ Quick win

Partial-write risk in atomic cache replacement.

replaceAllCachedPhotos clears the cache and then writes all photos via Promise.all. If any single setItem call fails mid-flight, the outer catch aborts the entire operation, leaving the cache cleared but only partially refilled.

While the cache is non-fatal and the network remains the source of truth, a partial write means users see incomplete cached data (longer skeleton screens) on the next visit until the network fetch completes.

🛡️ Suggested improvement

Use Promise.allSettled instead of Promise.all so individual write failures don't abort the batch:

 export async function replaceAllCachedPhotos(
photos: CachedPhoto[],
): Promise<void> {
try {
await store.clear();
- await Promise.all(+ const results = await Promise.allSettled(
photos.map((p) => store.setItem(p.metaEnvelopeId, p)),
);
+ const failures = results.filter((r) => r.status === "rejected");+ if (failures.length > 0) {+ console.warn(`[photoCache] ${failures.length} photo(s) failed to cache`);+ }
} catch {
// non-fatal
}
}

This allows partial success—most photos are cached even if a few writes fail.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 82 - 93,
The current replaceAllCachedPhotos implementation clears the store then uses
Promise.all for store.setItem calls which can leave the cache partially refilled
if one write fails; change the writes to use Promise.allSettled so individual
setItem failures don't reject the whole batch (call store.clear(), then await
Promise.allSettled(photos.map(p => store.setItem(p.metaEnvelopeId, p))) and
optionally inspect results to log or ignore rejected entries) while keeping the
outer try/catch non-fatal.

43-48: ⚡ Quick win

Consider optimizing photo retrieval by ename.

getCachedPhotosForEname loads all cached photos via getAllCachedPhotos(), then filters by ename in memory. If the cache accumulates many photos across multiple users, this becomes inefficient.

Consider using localforage's iterate directly with early-exit or adding an IndexedDB index on ename for O(log n) lookup instead of O(n) scan.

⚡ Suggested optimization
 export async function getCachedPhotosForEname(
ename: string,
): Promise<CachedPhoto[]> {
- const all = await getAllCachedPhotos();- return all.filter((p) => p.ename === ename);+ try {+ const photos: CachedPhoto[] = [];+ await store.iterate<CachedPhoto, void>((value) => {+ if (value.ename === ename) {+ photos.push(value);+ }+ });+ return photos;+ } catch {+ return [];+ }
}

This avoids loading unrelated photos into memory. For a more robust solution, consider creating a localforage instance with an IndexedDB index on ename.

🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts` around lines 43 - 48,
The current getCachedPhotosForEname function loads everything via
getAllCachedPhotos then filters in memory; change it to iterate the underlying
store (e.g., localforage.iterate) and collect only entries whose p.ename ===
ename, returning early if you can satisfy any expected limit, so you avoid O(n)
memory allocation; alternatively create a separate localforage instance or
IndexedDB index keyed by ename and query that store instead of calling
getAllCachedPhotos — update the implementation of getCachedPhotosForEname to use
localforage.iterate (or the new indexed store) and remove the
getAllCachedPhotos-dependent filtering.
infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte (1)

155-204: ⚡ Quick win

Consider using the vaultGqlRequest helper for consistency.

loadBindingDocuments constructs inline fetch() calls with manual header building and query strings (lines 155-176). The codebase already provides a vaultGqlRequest helper (imported from $lib/utils at line 42) that handles headers, error parsing, and auth tokens.

Using the helper would reduce duplication and keep the GraphQL request pattern consistent across the eID wallet module.

♻️ Suggested refactor

Replace the inline fetch with vaultGqlRequest:

+ import { vaultGqlRequest } from "$lib/utils/socialBinding";++ const BINDING_DOCS_BY_TYPE_QUERY = `+ query($type: BindingDocumentType!) {+ bindingDocuments(type: $type, first: 50) {+ edges { node { id parsed } }+ }+ }+ `;+
async function loadBindingDocuments(): Promise<void> {
if (!globalState) return;
const vault = await globalState.vaultController.vault;
if (!vault?.uri || !vault?.ename) return;
- const enameHeader = vault.ename.startsWith("@")- ? vault.ename- : `@${vault.ename}`;
const gqlUrl = new URL("/graphql", vault.uri).toString();
-- const headers: Record<string, string> = {- "Content-Type": "application/json",- "X-ENAME": enameHeader,- ...(PUBLIC_EID_WALLET_TOKEN- ? { Authorization: `Bearer ${PUBLIC_EID_WALLET_TOKEN}` }- : {}),- };- const typedQuery = (type: string) =>- JSON.stringify({- query: `query($type: BindingDocumentType!) {- bindingDocuments(type: $type, first: 50) {- edges { node { id parsed } }- }- }`,- variables: { type },- });+ const ownerEname = vault.ename;
try {
const [idDocRes, selfRes] = await Promise.all([
- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("id_document") }),- fetch(gqlUrl, { method: "POST", headers, body: typedQuery("self") }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "id_document" }),+ vaultGqlRequest(gqlUrl, ownerEname, BINDING_DOCS_BY_TYPE_QUERY, { type: "self" }),
]);
- const parseEdges = async (res: Response) => {- const json = await res.json();- return (json?.data?.bindingDocuments?.edges ?? []) as {- node: { id: string; parsed: ParsedBindingDoc | null };- }[];- };-- const [idDocEdges, selfEdges] = await Promise.all([- parseEdges(idDocRes),- parseEdges(selfRes),- ]);+ const idDocEdges = idDocRes.bindingDocuments?.edges ?? [];+ const selfEdges = selfRes.bindingDocuments?.edges ?? [];
const idDocEntry = idDocEdges.find((e) => e.node.parsed?.type === "id_document");
legalId = idDocEntry?.node.parsed ? toLegalIdDoc(idDocEntry.node.parsed) : null;
🤖 Prompt for 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.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte around lines
155 - 204, Replace the two manual fetch calls and header construction in
loadBindingDocuments with the existing vaultGqlRequest helper: stop building
headers and Authorization using PUBLIC_EID_WALLET_TOKEN and instead call
vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for the
"id_document" and "self" queries (reuse the existing typedQuery string/variables
shape), then feed the returned Response/JSON into the existing parseEdges logic
(or adapt parseEdges to accept the vaultGqlRequest result). This keeps
auth/error handling consistent and removes the inline fetch/header duplication
(references: vaultGqlRequest, typedQuery, parseEdges, idDocEntry/selfEntry).
🤖 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.
Nitpick comments:
In `@infrastructure/eid-wallet/src/lib/utils/photoCache.ts`:
- Around line 82-93: The current replaceAllCachedPhotos implementation clears
the store then uses Promise.all for store.setItem calls which can leave the
cache partially refilled if one write fails; change the writes to use
Promise.allSettled so individual setItem failures don't reject the whole batch
(call store.clear(), then await Promise.allSettled(photos.map(p =>
store.setItem(p.metaEnvelopeId, p))) and optionally inspect results to log or
ignore rejected entries) while keeping the outer try/catch non-fatal.
- Around line 43-48: The current getCachedPhotosForEname function loads
everything via getAllCachedPhotos then filters in memory; change it to iterate
the underlying store (e.g., localforage.iterate) and collect only entries whose
p.ename === ename, returning early if you can satisfy any expected limit, so you
avoid O(n) memory allocation; alternatively create a separate localforage
instance or IndexedDB index keyed by ename and query that store instead of
calling getAllCachedPhotos — update the implementation of
getCachedPhotosForEname to use localforage.iterate (or the new indexed store)
and remove the getAllCachedPhotos-dependent filtering.
In `@infrastructure/eid-wallet/src/routes/`(app)/main/+page.svelte:
- Around line 155-204: Replace the two manual fetch calls and header
construction in loadBindingDocuments with the existing vaultGqlRequest helper:
stop building headers and Authorization using PUBLIC_EID_WALLET_TOKEN and
instead call vaultGqlRequest(gqlUrl, { query: ..., variables: { type } }) for
the "id_document" and "self" queries (reuse the existing typedQuery
string/variables shape), then feed the returned Response/JSON into the existing
parseEdges logic (or adapt parseEdges to accept the vaultGqlRequest result).
This keeps auth/error handling consistent and removes the inline fetch/header
duplication (references: vaultGqlRequest, typedQuery, parseEdges,
idDocEntry/selfEntry).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f547d467-77a0-42d7-9d93-b0263d25b9a9

📥 Commits

Reviewing files that changed from the base of the PR and between 972f1f6 and 4fe6241.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (7)
  • infrastructure/eid-wallet/package.json
  • infrastructure/eid-wallet/src/lib/utils/personalBinding.ts
  • infrastructure/eid-wallet/src/lib/utils/photoCache.ts
  • infrastructure/eid-wallet/src/lib/utils/socialBinding.ts
  • infrastructure/eid-wallet/src/routes/(app)/main/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/personal/+page.svelte
  • infrastructure/eid-wallet/src/routes/(app)/settings/+page.svelte

@coodos
coodos merged commit f31d097 into mainJun 5, 2026
4 checks passed
@coodos
coodos deleted the fix/loadingIssue branch June 5, 2026 09:31
@coderabbitaicoderabbitaiBot mentioned this pull request Jul 15, 2026
6 tasks
Sign up for freeto 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.

eID Wallet: Improve UX for blocking loads on login

2 participants

@Sahil2004@coodos