fix: subfolder navigation after reload with E2E coverage#70
Conversation
Two-task plan: implement loadFolder stub in folder.service.ts and wire navigateTo in useFolderNavigation.ts to unwrap keys, resolve IPNS, decrypt metadata, and populate FolderNode in Zustand store. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ta decryption - Replace TODO stub with real implementation that resolves IPNS, fetches and decrypts folder metadata, and returns complete FolderNode - Add parentId and name parameters for correct FolderNode construction - Import resolveIpnsRecord from ipns.service - Gracefully handle IPNS-not-found (returns empty folder with warning) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace setTimeout stub with real subfolder loading pipeline - Unwrap ECIES-encrypted folderKey and ipnsPrivateKey from FolderEntry - Call loadFolder to resolve IPNS, fetch and decrypt metadata - Set loading placeholder in store while async operation runs - Use getState() pattern throughout to avoid stale Zustand closures - Gracefully handle errors by removing placeholder on failure - Make navigateTo async, update return type to Promise<void> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tasks completed: 2/2 - Implement loadFolder with IPNS resolution and metadata decryption - Wire navigateTo to load subfolders with key unwrapping SUMMARY: .planning/quick/003-fix-subfolder-navigation-and-upload/003-SUMMARY.md Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Implemented real subfolder loading: ECIES key unwrapping, IPNS resolution, and metadata decryption when navigating into subfolders. Fixes breadcrumbs not updating and "parent folder not found" error on upload. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
WalkthroughImplements a real subfolder load pipeline: ECIES key unwrapping, IPNS resolution, encrypted metadata fetch/decrypt, and population of the Zustand store with complete FolderNode data. The navigateTo hook becomes asynchronous and planning docs updated to mark Quick Task 003 complete. Changes
Sequence DiagramsequenceDiagram
participant User
participant Hook as useFolderNavigation
participant Store as Zustand\ Store
participant Service as folder.service
participant IPNS as ipns.service
participant IPFS as IPFS
participant Crypto as Crypto\ Utils
User->>Hook: Click subfolder (navigateTo)
Hook->>Store: Read current folders (getState)
Hook->>Hook: Find FolderEntry for target
Hook->>Crypto: Get derived keypair (useAuthStore)
Hook->>Store: Insert loading placeholder & navigate
Hook->>Crypto: unwrapKey(folderKey)
Hook->>Crypto: unwrapKey(ipnsPrivateKey)
Hook->>Service: loadFolder(id, folderKey, ipnsPrivateKey, ipnsName, parentId, name)
Service->>IPNS: resolveIpnsRecord(ipnsName)
alt IPNS found
IPNS-->>Service: CID & sequenceNumber
Service->>IPFS: fetch metadata (CID)
IPFS-->>Service: Encrypted metadata
Service->>Crypto: decrypt metadata with folderKey
Crypto-->>Service: Decrypted FolderMetadata
Service-->>Hook: FolderNode (with children, sequenceNumber)
else IPNS not found
IPNS-->>Service: null
Service-->>Hook: Empty/unloaded FolderNode (fallback)
end
Hook->>Store: Update FolderNode (if latest target)
Hook->>Store: Clear loading state
Hook-->>User: Subfolder rendered (breadcrumbs updated)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@apps/web/src/hooks/useFolderNavigation.ts`:
- Around line 156-162: The navigation happens before the folder placeholder is
inserted, causing a brief undefined currentFolder; fix by inserting the store
placeholder for the target folder before calling navigate (or move the
navigate() call to after the placeholder-insertion logic used at the later
block), i.e., in useFolderNavigation ensure the same placeholder creation
routine (the code at the placeholder insertion point) runs for targetFolderId
first (and updates currentFolderId/store), then call navigate('/files' or
`/files/${targetFolderId}`) so the component re-render finds the placeholder
already in the store.
- Around line 155-251: navigateTo currently uses a single hook-level isLoading
flag and sets placeholder keys to empty Uint8Array, which causes
stale-completion races and potential key-read issues; modify navigateTo to (1)
track the latest requested target with a ref (e.g., lastNavigatedRef) or create
an AbortController per navigation and abort previous controllers so that when
async work completes you ignore stale results and only call setIsLoading(false)
or setFolder when the ref/controller matches the current navigation, and (2)
ensure the loadingPlaceholder created in navigateTo still uses safe sentinel
values but update any code paths (especially functions that decrypt or traverse
children such as unwrapKey, loadFolder and any recursive folder readers) to
first check folderNode.isLoaded before reading folderKey/ipnsPrivateKey (or
throw/return early) so empty keys are never used for real decryption; also
removeFolder can remain as-is.
There was a problem hiding this comment.
Pull request overview
Implements real subfolder navigation in the web app by loading subfolder metadata (IPNS resolve → IPFS fetch → decrypt) and unwrapping per-subfolder keys during navigation, so folder state in the Zustand store is actually populated for breadcrumbs and operations like upload.
Changes:
- Implemented
loadFolderto resolve IPNS, fetch encrypted metadata from IPFS, decrypt it with the folder key, and return a fully populatedFolderNode. - Updated
useFolderNavigation.navigateToto unwrap ECIES-encrypted subfolder keys and callloadFolderwhen entering unloaded subfolders. - Cleaned up README sections and added planning artifacts documenting the quick task.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/web/src/services/folder.service.ts | Implements real folder loading via IPNS resolution and metadata decryption, returning populated FolderNodes. |
| apps/web/src/hooks/useFolderNavigation.ts | Makes navigation async and loads/unlocks subfolder state on-demand, storing it in Zustand for breadcrumbs & operations. |
| README.md | Removes outdated PoC/timeline content. |
| .planning/quick/003-fix-subfolder-navigation-and-upload/003-SUMMARY.md | Adds quick-task execution summary and decisions. |
| .planning/quick/003-fix-subfolder-navigation-and-upload/003-PLAN.md | Adds the executed plan used to implement the changes. |
| .planning/STATE.md | Updates project planning state to reflect completion of quick task 003. |
Credit ChainSafe Files as inspiration for the project. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add `text` language to all bare fenced code blocks in README.md - Change bold emphasis to heading for subtitle (MD036) - Add blank line before fenced code block in tests/e2e/README.md (MD031) - Add `text` language to tree structure block in tests/e2e/README.md (MD040) - Exclude Rust build artifacts from markdownlint via .markdownlintignore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Phase 3.5 E2E tests (3.7-3.10) that reload the page and verify subfolder navigation works from a cold Zustand store, exercising the full navigateTo path (IPNS resolve + key unwrapping + metadata decrypt). Also fix ESLint hanging by ignoring src-tauri/target/ (1,755 generated JS files from Rust build) and .learnings/ directories. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Fix race condition: track latest nav target with useRef, skip stale
completions when user rapidly navigates between folders
- Fix undefined flash: insert store placeholder before calling navigate()
so component re-render finds the folder node immediately
- Fix root fallthrough: special-case navigateTo('root') to return early
instead of falling into the subfolder key-unwrapping path
- Fix IPNS retry: return isLoaded: false when IPNS resolution returns
null so the folder can be retried on next navigation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@apps/web/src/hooks/useFolderNavigation.ts`:
- Around line 240-252: After calling loadFolder(...) check the returned
FolderNode's isLoaded flag before calling useFolderStore.getState().setFolder:
if folderNode.isLoaded === false (and latestNavTarget.current ===
targetFolderId) either schedule an automatic retry with exponential backoff
(e.g., retry loop with setTimeout, capped attempts, and re-calling loadFolder
using the same parameters) or set a pending UI state on the store (add a
"pending"/"isPendingLoad" flag) so the UI shows a retry message/spinner; ensure
you still respect latestNavTarget.current to avoid races and clear retries when
navigation changes.
In `@apps/web/src/services/folder.service.ts`:
- Around line 78-99: resolveIpnsRecord currently returns only a CID and
fetchAndDecryptMetadata trusts the decrypted payload — add client-side IPNS
signature verification: have the backend include the IPNS record signature and
signer info in the resolve response (so resolveIpnsRecord returns { cid,
signature, signerPubKey, sequence }), then in the folder loading flow (where
resolveIpnsRecord is called in folder.service.ts) verify the IPNS record by
calling a new or existing verifyIpnsRecordSignature(signature, signerPubKey,
cid, sequence) before passing the CID to fetchAndDecryptMetadata; if
verification fails, throw an Error and mark the folder as not loaded/retry (do
not call fetchAndDecryptMetadata). Ensure you reference resolveIpnsRecord,
fetchAndDecryptMetadata, and verifyIpnsRecordSignature when implementing.
🧹 Nitpick comments (1)
apps/web/src/hooks/useFolderNavigation.ts (1)
157-266:setIsLoadingis a React state setter — verify no update-after-unmount.If the user navigates away (unmounts the component) while the async
navigateTois in flight,setIsLoading(false)in thefinallyblock would fire on an unmounted component. While React 18+ no longer warns about this, the state update is wasted. A minor concern, but if you ever add cleanup logic, anisMountedref or AbortController would be the right pattern.
- updateFolderChildren now sets isLoaded: true — fixes post-reload subfolder navigation where root stayed isLoaded: false after sync, causing navigateTo to skip it when searching for FolderEntry - Add retry loop (3 attempts, 2s delay) in navigateTo when loadFolder returns isLoaded: false due to IPNS not yet propagated Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
loadFolderwith IPNS resolution and metadata decryption sonavigateToworks from a cold Zustand store (after page reload or direct URL access)src-tauri/target/(1,755 generated Rust build files) — lint drops from hanging indefinitely to ~5 secondsTest plan
page.reload()correctly loads content via IPNS resolve + key unwrappingpnpm lintcompletes in <10 seconds (was hanging before)🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes
Documentation
Tests
Chores