feat: SDK-owned read chain and resolved folder listings#589
Conversation
Entire-Checkpoint: a4cc7cd0630a
Entire-Checkpoint: f768c1ed29a3
Grounds the phase plan in live file:line call-site inventories, the ungated-internal-read-path finding (ROT-07 gate exists only in the web layer today), and the exact ba3e022 mirror-revert footprint. Entire-Checkpoint: c3685d93a469
Entire-Checkpoint: 7a5431485365
Entire-Checkpoint: a4dc8b23ab21
Entire-Checkpoint: f7f84466cae9
Entire-Checkpoint: 4c7b28737f74
…ved folder listings) Entire-Checkpoint: e283107cdb4f
Entire-Checkpoint: bd338bbf7e09
Entire-Checkpoint: 8867172d49cd
RED run confirms the SDK's internal navigate path (ensureFolderLoaded -> dfsFindFolder -> resolvePublishedNode) is currently ungated: - Test 1 (below-floor rejection): FAILS -- resolves fine instead of throwing SequenceRegressionError (no enforceResolved call exists yet) - Test 2 (enforceResolved called once, sourcing generation from the parent SealedChildRef mirror): FAILS -- enforceResolved is never invoked from the internal read path - Test 3 (fail-closed on signatureVerified=false, before any floor mutation): FAILS -- resolvePublishedNode discards signatureVerified entirely today 3 failed | 293 passed | 49 skipped (folder-listing-gate: 0/3 passing) Entire-Checkpoint: 7acfa7f68c62
….enforceResolved Closes the RESEARCH.md Pitfall 2 gap: the SDK's own navigate path (ensureFolderLoaded -> dfsFindFolder -> resolvePublishedNode) previously resolved every read-side IPNS record with ZERO ROT-07 anti-rollback gating -- the only working read-path gate lived entirely in apps/web/src/services/ipns.service.ts, which this phase eventually deletes. This lands the SDK-internal equivalent first so no shipped security guarantee regresses mid-cutover. - resolvePublishedNode now threads signatureVerified from sdkCore.resolveIpnsRecord (previously discarded) - dfsFindFolder's per-hop child loop gates through rotationHighWater.enforceResolved before trusting a child resolve, sourcing generation from childRef.generation (the PARENT SealedChildRef mirror), never the child's own envelope generation (Pitfall 3 / T-68.2-03) - ensureRootFolderState gates the root resolve the same way, sourcing generation from the in-memory folderTree entry (mirrors the existing write-path gate in reconcileFolderSequence, since root has no parent SealedChildRef) - Both sites fail closed on signatureVerified=false BEFORE calling enforceResolved (T-68.2-02, no floor mutation on an unverified record) and guard Number.MAX_SAFE_INTEGER overflow before Number() conversion - getWriteBodyParams (write-internal-only resolve) intentionally left ungated per D-05 pnpm --filter @cipherbox/sdk test -- --run folder-listing-gate: 296/296 passed pnpm --filter @cipherbox/sdk test -- --run client-rotation: 296/296 passed Entire-Checkpoint: b77befa2fb09
Entire-Checkpoint: cb42af6b0556
Entire-Checkpoint: 36f536a08877
RED: proves listFolder/listSharedFolder/ResolvedChild do not exist yet, and folder:loaded still emits raw SealedChildRef[] instead of ResolvedChild[] with kind pre-resolved. Entire-Checkpoint: eb0451130766
…lder) to the SDK GREEN: folder-listing.ts's resolveChildren resolves a folder's sealed children into ResolvedChild[] through a caller-injected gated resolve function. client.ts adds gatedResolveChild (per-child ROT-07 gate, mirroring dfsFindFolder's gate for listing resolves), resolveListingChildren (in-SDK cache keyed by ipnsName, invalidated by sequenceNumber), and the listFolder/listSharedFolder facade methods. listSharedFolder hoists the intermediate-folder walk from useSharedNavigationActions rather than wrapping navigateReadChain (which forces a file leaf). Entire-Checkpoint: fc2eed31da2e
Retype folder:loaded/folder:updated/sharedFolder:updated children from SealedChildRef[] to ResolvedChild[] in events.ts. Every emit site in client.ts now emits through resolveListingChildren (the same cache/gate listFolder uses) instead of raw SealedChildRef[]. resolveChildren now soft-skips any per-child resolve/unseal failure (absent record, ROT-07 rollback rejection, unverified signature, or a corrupted/unopenable read-body) instead of failing the whole listing -- a listing render must degrade gracefully when one sibling can't be verified; the already-loaded target folder itself remains gated via ensureFolderLoaded/dfsFindFolder (Plan 01, D-05). Updated 4 pre-existing tests whose fixtures use legacy pre-node/v3 SealedChildRef shapes (no readKeySealed) to assert the correct new behavior: an unresolvable legacy fixture soft-skips to an empty ResolvedChild[] in the event payload rather than passing the raw fixture through. adoptSharedFolderResult is now async (awaits the listing resolve); all call sites updated. updateSharedFile's file-only-publish emission explicitly invalidates the parent's listingCache entry before re-resolving, since a file-content-only republish does not bump the parent folder's own IPNS sequence (the cache's invalidation clock). Entire-Checkpoint: 33e8167dd6c6
Missed in the GREEN commit: index.ts must re-export the ResolvedChild type-only (D-07) so the web can type its listFolder/listSharedFolder render sites without reaching into the SDK's internal folder-listing module. Entire-Checkpoint: 6bfa9bf75366
Documents the ResolvedChild listing API, the event payload retype, and the widened per-child failure-handling deviation with rationale. Entire-Checkpoint: ead14078e4dd
Entire-Checkpoint: 33a0a40e4466
- Add uploadBytes/downloadBytes/unpin facade methods on CipherBoxClient wrapping sdkCore.addToIpfs/fetchFromIpfs/unpinFromIpfs - Forward onProgress through both upload and download paths so web progress bars keep working - Unit test asserts onProgress forwarding on upload and download Entire-Checkpoint: 28089239dd79
…tural utils - Add getFolderMetadata facade method delegating to the gated ensureFolderLoaded path (no gate bypass) - Re-export getDepth/isDescendantOf/calculateSubtreeDepth/ selectEncryptionMode from @cipherbox/sdk so the web can stop importing these pure utils from sdk-core directly (D-07) Entire-Checkpoint: 9b761b056b7e
Entire-Checkpoint: fd9a27194912
Entire-Checkpoint: ba99e913d864
… pending plan 11) Entire-Checkpoint: 4eeb1b1fc0d3
- Author owner+grantee multi-account Playwright spec proving the owner sees a grantee's upload into a shared folder without writing first - Assert size/modifiedAt render from the resolved listing (ResolvedChild), not the em-dash/epoch placeholders from the pre-fix mirror - Drive the proof via nav-triggered re-resolve (D-03), not the 30s poll - Expected-red until the Phase 68.2 web cutover (Plans 06-11); green is the Plan 12 phase gate Entire-Checkpoint: 5f412ece0e0d
Entire-Checkpoint: 0a6a61a73bdc
- CipherBoxClient.bootstrapVaultKeys/serializeVault/deserializeVault mediate the useAuth.ts vault-bootstrap crypto (initializeVault/encryptVaultKeys/ serializeVaultBlobV3/deserializeVaultBlobV3) inside the SDK - CipherBoxClient.publishEmptyRootNode wraps sdkCore.publishEmptyRootNode with this.ctx injected - CipherBoxClient.deriveRegistryIpnsKeypair/encryptRegistry/decryptRegistry mediate device-registry.service.ts's registry crypto - deserializeVault zeroes the already-unwrapped rootReadKey if the paired unwrapKey call fails (T-68.2-09 terminal-owner zeroing) - Re-export VaultInit/DeviceRegistry types from @cipherbox/sdk
- CipherBoxClient.testConnection wraps sdkCore.testConnection - CipherBoxClient.resolveConfigBlob/publishConfigBlob wrap sdkCore.resolveIpnsRecord/createAndPublishIpnsRecord with this.ctx injected -- deliberately NOT routed through the ROT-07 rotationHighWater.enforceResolved gate (BYO config blob is user-configured, not a rotation-governed node) - fetch/add for the config blob's raw bytes reuse the existing downloadBytes/uploadBytes facade methods (no config-blob-specific duplicates needed) - Re-export ConnectionTestResult type from @cipherbox/sdk
…ves) Entire-Checkpoint: f63dd04dbd95
Entire-Checkpoint: f1332575ed1a
- Adds folder-reresolve.test.ts proving ensureFolderLoaded/listFolder never re-resolve an already-loaded folder's IPNS record, closing the SDK-READ-03 self-referential cache-clock gap (68.2-VERIFICATION.md). - Adds forceResolve as an accepted-but-ignored no-op option on ensureFolderLoaded/listFolder so RED is a runtime failure, not a compile error. Entire-Checkpoint: 9ca8cc629473
…lders - ensureFolderLoaded/listFolder gain a forceResolve option: for an already-loaded folder, this routes to a new gated in-place re-resolve (reresolveFolderInPlace/doReresolveFolderInPlace) instead of the verbatim cached short-circuit, closing the SDK-READ-03 self- referential cache-clock gap (owner never seeing a grantee's later upload without writing first). - The re-resolve mirrors dfsFindFolder's gate exactly: fail-closed on an unverified record before any floor mutation, MAX_SAFE_INTEGER overflow guard, then RotationHighWater.enforceResolved sourced from existing.nodeGeneration (never the freshly relay-served envelope generation). A below-floor resolve still rejects with SequenceRegressionError and leaves the stored FolderState untouched. - FolderState is updated in place (single source of truth, D-09) -- no second cache or store. Concurrent forceResolve calls for the same ipnsName dedupe to one network resolve via reresolveInFlight. - The no-opts cached fast path used by write chokepoints (requireFolder etc.) is unchanged. Entire-Checkpoint: 7a7f8364ee58
Entire-Checkpoint: 7e575a1732ae
- useFolderNavigation.ts: refreshFolderListing (root + already-loaded legs)
and the cold-load navigateTo path (ensureFolderLoaded retry loop +
pre-store listFolder) now pass { forceResolve: true }
- useSyncPolling.ts: invalidateOpenFolder passes { forceResolve: true } to
both client.listFolder and client.ensureFolderLoaded
- write-mutation chokepoints (useFileBrowserActions.ts, folder-helpers.ts)
left on the cached fast path, unchanged
- updates useSyncPolling.test.ts mock-call assertions for the new option arg
Entire-Checkpoint: 022a3dd337a4
Entire-Checkpoint: b42aabcbea50
…eeded — CI web-e2e re-confirm) Entire-Checkpoint: 5affdba6e4e4
…GRATIONS.md Entire-Checkpoint: 5478b085a2ba
…sions Gap-closure round 2 for two web-layer regressions the 68.2 read-chain migration introduced (VERIFICATION gaps; SDK-READ-01..04 unaffected, no SDK change). Regression A - after the 68.2-11 kind-cache removal, isFileRef(bareSealedChildRef) always returns false, so identity-only refs classified as folders. This hid the selection-bar download button and context-menu Download/Edit/Preview items AND broke the download logic itself: handleDownload/handleBatchDownload filtered every selected file out. Fixed by classifying against the SDK-resolved ResolvedChild listing (new isFileRefResolved helper, keyed by ipnsName) at every download-gating site across the owned-vault and shared browsers. Regression B - an upload into a subfolder never appeared: UploadZone targets the open folder (currentFolderId) but onUploadComplete only called handleSync, which resyncs root only. Fixed by refreshing the open folder immediately via the existing invalidateOpenFolder(), then the best-effort root/tree resync. Also force live resolves on the root resync and post-409 resyncFolder for deterministic-freshness consistency with the nav/poll precedent. Verified on a fresh-container stack: batch-download 5/5, writable-shares 29/29, shared-folder-desync 4/4 (SC#5 guard holds). tsc + eslint clean; D-07 boundary grep gate green (new imports are type-only from the @cipherbox/sdk facade). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B7XhtqxJ7pAYkZTmdmKerE Entire-Checkpoint: a2fc426e2121
…gressions Plans + summaries for the two VERIFICATION-gap regressions closed in gap-closure round 2 (fix 0577a34): Regression A (isFileRef kind classification after the kind-cache removal, plan 15) and Regression B (post-upload refresh targeting root instead of the open subfolder, plan 16). UAT records the fresh-container verification: batch-download 5/5, writable-shares 29/29, shared-folder-desync 4/4. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B7XhtqxJ7pAYkZTmdmKerE Entire-Checkpoint: e139d069f3c6
Re-verify after gap-closure round 2: VERIFICATION.md -> passed (5/5 must-haves; the prior human_needed e2e item is now executed-green on fresh containers). VALIDATION.md -> compliant, 0 gaps. SECURITY.md -> SECURED, 17/17 threats closed. REQUIREMENTS traceability table marks SDK-READ-01..04 Complete. Defers two low-priority todos surfaced during ship: SharedFolderRow drag-payload kind classification (harmless — type unused by the shared drop handler) and promoting the D-07 web/SDK boundary grep gate to an ESLint/CI rule. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B7XhtqxJ7pAYkZTmdmKerE Entire-Checkpoint: 64dab836dec3
Applied the clear-cut, low-risk in-scope findings from the local CodeRabbit review of the phase branch: - SDK: destroy() now clears the new listingCache + reresolveInFlight maps so no resolved listing (private folder structure) or in-flight re-resolve closure survives client teardown. - SDK: in the descendSharedChild hop loop, adopt the minted childReadKey into mintedReadKey BEFORE unsealNode so the outer finally zeroes it even if unsealNode throws (leak-on-failure fix; terminal-owner buffer, safe to zero). - web: route handleSync's forced ensureFolderLoaded through runWithFailureUx so a gate rejection surfaces the D-05 toast, matching the listFolder leg. - web: resyncFolder re-reads the store after its awaits and skips the writeback if the folder was navigated away/removed (matches refreshFolderListing). - web: search-index indexes children by their resolved ResolvedChild.kind instead of hardcoding 'file', so subfolders no longer index as files. Deferred the risky/out-of-scope findings as todos (noted in the PR): gating the non-listing read facades (resolveNodeIdentity/resolveFileMetadata) with the ROT-07 floor, and a hardening backlog (owned listingCache invalidation, toResolvedChildView unresolved state, invalidateOpenFolder monotonicity, refreshSharedFolder write envelope, seq safe-integer, empty-state a11y, test hardening). SDK suite 338 passed/49 skipped; web tsc + eslint clean; D-07 gate green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B7XhtqxJ7pAYkZTmdmKerE Entire-Checkpoint: ceb730025e73
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (8)
✅ Files skipped from review due to trivial changes (4)
🚧 Files skipped from review as they are similar to previous changes (4)
WalkthroughMoves gated IPNS read-chain enforcement and resolved-child projection into the SDK, adds SDK facades for transport and vault/registry flows, retypes folder events to ChangesSDK-owned read chain and resolved folder listings
Estimated code review effort: 5 (Critical) | ~150 minutes Sequence Diagram(s)sequenceDiagram
participant WebApp
participant CipherBoxClient
participant RotationHighWater
participant SdkCore
WebApp->>CipherBoxClient: listFolder(ipnsName, { forceResolve })
CipherBoxClient->>SdkCore: resolveIpnsRecord(ipnsName)
SdkCore-->>CipherBoxClient: published, sequenceNumber, signatureVerified
CipherBoxClient->>RotationHighWater: enforceResolved(generation, seq, versionFloor)
RotationHighWater-->>CipherBoxClient: ok or SequenceRegressionError
CipherBoxClient->>CipherBoxClient: resolveChildren(children, parentReadKey)
CipherBoxClient-->>WebApp: ResolvedChild[]
sequenceDiagram
participant UploadZone
participant useSyncPolling
participant FolderStore
participant CipherBoxClient
UploadZone->>useSyncPolling: invalidateOpenFolder()
useSyncPolling->>CipherBoxClient: listFolder / ensureFolderLoaded (forceResolve)
CipherBoxClient-->>useSyncPolling: children, rawChildren, sequenceNumber
useSyncPolling->>FolderStore: update projection fields
Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed: dependency version conflict. Check your lock file or package.json. 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 |
Release Preview
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #589 +/- ##
===========================================
+ Coverage 65.13% 79.93% +14.80%
===========================================
Files 152 117 -35
Lines 13977 10333 -3644
Branches 1603 1698 +95
===========================================
- Hits 9104 8260 -844
+ Misses 4631 1828 -2803
- Partials 242 245 +3
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
Greptile SummaryThis PR moves the gated IPNS read chain (ROT-07 anti-rollback, fail-closed on unverified signatures) entirely into
Confidence Score: 4/5Safe to merge with one real-state gap in the private-vault file browser: mutations other than upload leave the FileList item source stale for up to 30 s. The SDK read chain, ROT-07 gating, key lifecycle, shared-folder listing, and D-07 boundary enforcement all look correct. The gap is in the folder:updated event subscription in folder.store.ts: it updates the resolved display projection (children) but never calls updateFolderRawChildren, so FileBrowser.tsx's rawChildren-based FileList.items and hasChildren gate go stale after delete, create, rename, or move — showing ghost rows for deleted items and hiding newly created ones until the next sync tick. Upload is handled correctly via explicit invalidateOpenFolder; the mutation path is the gap. apps/web/src/stores/folder.store.ts (folder:updated subscription does not call updateFolderRawChildren) and apps/web/src/hooks/useFolderMutations.ts / useFileBrowserActions.ts (no post-mutation rawChildren refresh). Important Files Changed
Sequence Diagram%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant Web as Web (FileBrowser)
participant Store as folder.store
participant SDK as CipherBoxClient
participant Net as IPFS/IPNS
Note over Web,Net: Folder load (nav re-resolve)
Web->>SDK: listFolder(ipnsName, forceResolve)
SDK->>Net: resolveIpnsRecord + fetchFromIpfs
SDK->>SDK: gatedResolveChild (ROT-07)
SDK->>SDK: "resolveListingChildren -> ResolvedChild[]"
SDK-->>Web: ResolvedChild[]
Web->>SDK: ensureFolderLoaded(ipnsName, forceResolve)
SDK-->>Web: FolderState (SealedChildRef[])
Web->>Store: updateFolderChildren(resolved)
Web->>Store: updateFolderRawChildren(raw)
Note over Web,Net: Folder mutation (delete/create/move)
Web->>SDK: deleteToBin / createFolder / renameItem
SDK->>Net: publish new folder state
SDK->>SDK: "resolveListingChildren -> ResolvedChild[]"
SDK-->>Store: folder:updated (ResolvedChild[])
Store->>Store: updateFolderChildren only
Note over Store: rawChildren NOT updated until next 30 s poll
Note over Web,Net: 30 s poll leg (D-03)
Web->>SDK: invalidateOpenFolder()
SDK-->>Web: ResolvedChild[] + FolderState
Web->>Store: updateFolderChildren + updateFolderRawChildren
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
participant Web as Web (FileBrowser)
participant Store as folder.store
participant SDK as CipherBoxClient
participant Net as IPFS/IPNS
Note over Web,Net: Folder load (nav re-resolve)
Web->>SDK: listFolder(ipnsName, forceResolve)
SDK->>Net: resolveIpnsRecord + fetchFromIpfs
SDK->>SDK: gatedResolveChild (ROT-07)
SDK->>SDK: "resolveListingChildren -> ResolvedChild[]"
SDK-->>Web: ResolvedChild[]
Web->>SDK: ensureFolderLoaded(ipnsName, forceResolve)
SDK-->>Web: FolderState (SealedChildRef[])
Web->>Store: updateFolderChildren(resolved)
Web->>Store: updateFolderRawChildren(raw)
Note over Web,Net: Folder mutation (delete/create/move)
Web->>SDK: deleteToBin / createFolder / renameItem
SDK->>Net: publish new folder state
SDK->>SDK: "resolveListingChildren -> ResolvedChild[]"
SDK-->>Store: folder:updated (ResolvedChild[])
Store->>Store: updateFolderChildren only
Note over Store: rawChildren NOT updated until next 30 s poll
Note over Web,Net: 30 s poll leg (D-03)
Web->>SDK: invalidateOpenFolder()
SDK-->>Web: ResolvedChild[] + FolderState
Web->>Store: updateFolderChildren + updateFolderRawChildren
Reviews (3): Last reviewed commit: "fix(68.2): address second-round CodeRabb..." | Re-trigger Greptile |
- SDK (P1): do not cache a PARTIAL folder listing. resolveListingChildren cached the result of resolveChildren even when a child was dropped (transient network / ROT-07 / AEAD failure), pinning the incomplete listing at that sequenceNumber until a remote write or reload -- even the forceResolve poll/nav paths reuse a same-sequence entry. Skip the cache-set when resolved.length < children.length so a later resolve reattempts the dropped child and self-heals. - web (P2): memoize SelectionActionBar's resolvedByIpnsName map so it is not reallocated on every render (resolvedChildren is a fresh array reference each parent render). SDK suite 338 passed/49 skipped; web tsc + eslint clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B7XhtqxJ7pAYkZTmdmKerE Entire-Checkpoint: a1094715b3eb
Decisions (single gated read entrypoint, D-07 full boundary, ResolvedChild, gate-then-delete ordering, revert-mirror-last), lessons (post-mutation refresh targets the open folder, isFileRef vs bare refs, don't cache partial listings, tee-republish infra gate, large-PR review skips, terminal-owner zeroization), patterns, and surprises from the phase. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B7XhtqxJ7pAYkZTmdmKerE Entire-Checkpoint: feb45dbc0e6a
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
tests/web-e2e/page-objects/file-browser/file-list.page.ts (1)
155-164: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the em-dash placeholder in
getItemType
getItemTypestill checks for'-', butFileListItem.tsxrenders folders with—, so the helper returns'file'for folders. Update the comparison and docstring to match the current UI.🤖 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 `@tests/web-e2e/page-objects/file-browser/file-list.page.ts` around lines 155 - 164, Update the FileBrowser page object helper so getItemType matches the current FileListItem UI: it should treat the em-dash placeholder as a folder, not the hyphen, and the comment above the method should describe that same behavior. Use the getItemType method and the .file-list-item-size locator to locate the logic, and change the comparison to align with the folder rendering used by FileListItem.tsx.packages/sdk/src/client.ts (1)
3583-3627: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
maybeRepublishFolderForFileMigrationemits stale listing data after a file-only publish — missing the cache invalidation applied to the equivalent shared-file path.This is the emission path for
replaceFile/restoreFileVersion/deleteFileVersion. WhenmigratedIpnsPrivateKeyEncryptedis absent (the common case), the folder is not republished, solive.sequenceNumberis unchanged.resolveListingChildren(live.children, live.folderKey, folderIpnsName, live.sequenceNumber)then hitslistingCacheat the SAME(ipnsName, sequenceNumber)key that was cached before the file's content/size/modifiedAt changed — so the emittedfolder:updatedevent serves staleResolvedChilddata for the just-updated file.Compare with the shared-file counterpart,
updateSharedFile(Lines 5024–5047), which explicitly guards against this exact scenario:"the just-updated FILE's own PublishedNode (content/modifiedAt) is now stale in
listingCacheif it was resolved before this update. Invalidate the parent's cache entry..."and calls
this.listingCache.delete(live.ipnsName)before resolving. The owned-file path never applies the same fix, so replacing a file, restoring a version, or deleting a version silently leaves stale size/modifiedAt in the web's resolved listing until an unrelated folder-level mutation happens to bump the sequence.🩹 Proposed fix
const live = this.folderTree.get(folderIpnsName) ?? folder; + // A file-only publish (replaceFile/restoreFileVersion/deleteFileVersion) + // changes the FILE's own PublishedNode without bumping the PARENT + // folder's sequenceNumber -- invalidate the cache entry so the + // resolve below re-derives every child instead of serving a stale + // cached size/modifiedAt for the just-updated file (mirrors the + // updateSharedFile fix at 68.2-02 Rule 1). + this.listingCache.delete(folderIpnsName); this.emitter.emit({ type: 'folder:updated', folderId: folderIpnsName, ipnsName: folderIpnsName, children: await this.resolveListingChildren( live.children, live.folderKey, folderIpnsName, live.sequenceNumber ), sequenceNumber: live.sequenceNumber, });🤖 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 `@packages/sdk/src/client.ts` around lines 3583 - 3627, The folder update emission in maybeRepublishFolderForFileMigration can return stale listing data when no republish happens because resolveListingChildren reuses listingCache at the same ipnsName/sequenceNumber key. Mirror the cache invalidation used in updateSharedFile by clearing the relevant listing cache entry before resolving and emitting folder:updated for the common file-only path, then re-resolve children from the fresh state using live folder data.
🧹 Nitpick comments (4)
apps/web/src/components/file-browser/ContextMenu.tsx (1)
99-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDuplicate
ipnsName → ResolvedChildmap construction across components.The same
new Map(resolvedChildren.map((r) => [r.ipnsName, r]))pattern also appears inFileList.tsx(and likely other file-browser components in this cohort). Consider extracting a small shared helper (e.g., alongsideisFileRefResolvedinfileTypes.ts) to avoid re-implementing this lookup in each component.♻️ Suggested shared helper
+// apps/web/src/utils/fileTypes.ts +export function buildResolvedByIpnsName(resolvedChildren: ResolvedChild[]): Map<string, ResolvedChild> { + return new Map(resolvedChildren.map((r) => [r.ipnsName, r])); +}- const resolvedByIpnsName = useMemo( - () => new Map((resolvedChildren ?? []).map((r) => [r.ipnsName, r])), - [resolvedChildren] - ); + const resolvedByIpnsName = useMemo( + () => buildResolvedByIpnsName(resolvedChildren ?? []), + [resolvedChildren] + );🤖 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 `@apps/web/src/components/file-browser/ContextMenu.tsx` around lines 99 - 101, Duplicate ipnsName to ResolvedChild map construction is repeated in ContextMenu and other file-browser components. Extract the lookup logic into a shared helper near isFileRefResolved in fileTypes.ts, then update ContextMenu and FileList to use that helper instead of rebuilding the Map inline. Keep the helper focused on taking resolvedChildren and returning the ipnsName keyed lookup so all components share the same implementation.apps/web/src/components/file-browser/useFileBrowserActions.ts (1)
152-173: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win
handleSyncresolves the same folder twice.listFolder(rootIpnsName, { forceResolve: true })already advances the cachedFolderState; the follow-upensureFolderLoaded(..., { forceResolve: true })does another network re-resolve and can makechildrenandrawChildrencome from different snapshots if a publish lands between awaits. Resolve once, then reuse the already-loaded folder state for the second read.🤖 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 `@apps/web/src/components/file-browser/useFileBrowserActions.ts` around lines 152 - 173, handleSync is resolving the same folder twice, which can produce mismatched snapshots between children and rawChildren. Update the logic in useFileBrowserActions.handleSync so listFolder(rootIpnsName, { forceResolve: true }) performs the single resolve, then reuse that same FolderState result instead of calling ensureFolderLoaded(..., { forceResolve: true }) again. Keep the updates to updateFolderChildren, updateFolderRawChildren, and updateFolderSequence sourced from the one resolved state.packages/sdk/src/__tests__/client-shared-write.test.ts (1)
132-143: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTest coverage regression:
sharedFolder:updated.childrenassertions reduced to trivial[]checks.These three tests now assert
updated.childrenequals[]because the legacyPUBLISHED_CHILDREN/REFRESHED_CHILDRENfixtures lackreadKeySealedand are soft-skipped by the resolve path — the tests no longer verify thatsharedFolder:updatedactually carries resolvedResolvedChild[]content for real children.folder-listing.test.ts'sbuildChildFixture(usingsealNode/sealChildReadKey) already shows the pattern for building fixtures the resolver can open; consider reusing it here so these assertions test real resolution instead of the empty-array fallback path.Also applies to: 192-202, 225-233
🤖 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 `@packages/sdk/src/__tests__/client-shared-write.test.ts` around lines 132 - 143, The sharedFolder:updated tests are now only checking the empty-array fallback instead of real resolved children, so update the fixtures used in client-shared-write.test to produce resolver-openable children with readKeySealed. Reuse the fixture-building pattern from folder-listing.test and buildChildFixture (including sealNode/sealChildReadKey) so sharedFolder:updated.children is asserted as actual ResolvedChild[] content rather than [] in the affected test cases.Source: Path instructions
packages/sdk/src/__tests__/client.test.ts (1)
252-261: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSame test-quality gap as
client-shared-write.test.ts:folder:loaded.childrenassertion reduced to[].The
mockChildrenfixture lacksreadKeySealed, so it's soft-skipped and the assertion only proves the skip path works, not thatfolder:loadedcarries correctly resolvedResolvedChild[]data. Consider reusing thebuildChildFixturepattern fromfolder-listing.test.tsto assert meaningful resolved content here too.🤖 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 `@packages/sdk/src/__tests__/client.test.ts` around lines 252 - 261, The `folder:loaded.children` check in `client.test.ts` is too weak because `mockChildren` is being soft-skipped, so it only verifies the empty-skip path instead of real resolved payloads. Update the `folder:loaded` assertion in the relevant test to use a properly resolved fixture, following the `buildChildFixture` pattern from `folder-listing.test.ts`, and assert the actual `ResolvedChild[]` content via `loadedEvent.children` rather than expecting an empty array.Source: Path instructions
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.planning/STATE.md:
- Around line 29-34: The Phase 68.2 status snapshot is stale and no longer
matches the roadmap progress. Update the state block in STATE.md and the
corresponding roadmap entry in ROADMAP.md so they reflect 14 of 14 plans
complete and Completed 68.2-14-PLAN.md, keeping the Phase 68.2 summary, plan
count, and completion text consistent across both documents.
In `@apps/web/src/components/file-browser/DetailsDialog.tsx`:
- Around line 52-78: The dialog classification in DetailsDialog is using only
folderStore membership via isFolderHeuristic, which can misidentify resolved
folders as files. Update the logic around resolvedItem in DetailsDialog so the
SDK-authoritative resolvedChildren entry drives the kind when present, and use
the folderStore heuristic only as a fallback when no resolved child exists. Make
sure the downstream decisions that depend on isFolderHeuristic, including the
title, effect behavior, and FileDetails/FolderDetails selection, reference the
resolved kind first and keep the existing fallback behavior for missing resolved
items.
In `@apps/web/src/components/file-browser/FileList.tsx`:
- Around line 23-36: `toResolvedChildView` is using `modifiedAt: 0` as a
fallback sentinel, but `FileListItem` treats that as a valid timestamp and shows
a bogus 1970 date instead of the placeholder. Update the display logic in
`FileListItem` (the `dateDisplay`/`sizeDisplay` handling) to treat missing
unresolved entries consistently with `size: undefined`, and render the “—”
placeholder when `modifiedAt` is absent or represents the fallback sentinel
rather than a real timestamp.
In `@apps/web/src/hooks/useFolderNavigation.ts`:
- Around line 59-82: The refreshFolderListing function is doing two forced
re-resolves during cold navigation by calling both client.listFolder(...) and
client.ensureFolderLoaded(..., { forceResolve: true }), which duplicates the
IPNS work. Update refreshFolderListing to avoid the second forced resolve by
either removing forceResolve from the ensureFolderLoaded call or reusing the
state returned from the first refresh path, while preserving the existing
store.updateFolderChildren, store.updateFolderRawChildren, and
store.updateFolderSequence updates.
In `@apps/web/src/hooks/useSharedNavigation.ts`:
- Around line 355-375: The resolved-listing projection in useSharedNavigation is
running before the shared folder seed is complete, so
listSharedFolder(currentShareId, []) can see an uninitialized sharedFolderTree
and leave resolvedChildren empty or stale. Update the flow so
seedActiveSharedFolder completes before this effect can project, or gate the
effect on seed completion using the existing useEffect logic around currentView,
currentShareId, hasSdkClient(), and listSharedFolder. Keep the projection tied
to the seeded state so it only runs once the shared tree is ready.
In `@packages/sdk/src/client.ts`:
- Around line 3873-3925: The resolveFileMetadata path is bypassing the ROT-07
anti-rollback gate by resolving fileRef.ipnsName directly. Update
resolveFileMetadata to go through gatedResolveChild(fileRef) before unsealing
and calling sdkCore.resolveFileMetadata, matching the same enforcement used by
resolveChildIdentity and descendSharedChild. Keep the existing readKey
recovery/zeroing flow, but ensure the gated child resolution is the source of
the validated node metadata.
In `@packages/sdk/src/folder-listing.ts`:
- Around line 87-127: `resolveChildren` is doing per-child resolution
sequentially, which creates avoidable N-round-trip blocking on large folders.
Refactor the child loop in `resolveChildren` to run `gatedResolve`,
`unsealChildReadKey`, and `unsealNode` through a bounded-concurrency limiter
(reuse the existing `p-limit` pattern used elsewhere, and thread the limiter in
from `resolveListingChildren`). Keep the current skip-on-failure semantics and
`childReadKey` cleanup, but make sure the new implementation preserves result
ordering by `children` input while avoiding serial awaits.
---
Outside diff comments:
In `@packages/sdk/src/client.ts`:
- Around line 3583-3627: The folder update emission in
maybeRepublishFolderForFileMigration can return stale listing data when no
republish happens because resolveListingChildren reuses listingCache at the same
ipnsName/sequenceNumber key. Mirror the cache invalidation used in
updateSharedFile by clearing the relevant listing cache entry before resolving
and emitting folder:updated for the common file-only path, then re-resolve
children from the fresh state using live folder data.
In `@tests/web-e2e/page-objects/file-browser/file-list.page.ts`:
- Around line 155-164: Update the FileBrowser page object helper so getItemType
matches the current FileListItem UI: it should treat the em-dash placeholder as
a folder, not the hyphen, and the comment above the method should describe that
same behavior. Use the getItemType method and the .file-list-item-size locator
to locate the logic, and change the comparison to align with the folder
rendering used by FileListItem.tsx.
---
Nitpick comments:
In `@apps/web/src/components/file-browser/ContextMenu.tsx`:
- Around line 99-101: Duplicate ipnsName to ResolvedChild map construction is
repeated in ContextMenu and other file-browser components. Extract the lookup
logic into a shared helper near isFileRefResolved in fileTypes.ts, then update
ContextMenu and FileList to use that helper instead of rebuilding the Map
inline. Keep the helper focused on taking resolvedChildren and returning the
ipnsName keyed lookup so all components share the same implementation.
In `@apps/web/src/components/file-browser/useFileBrowserActions.ts`:
- Around line 152-173: handleSync is resolving the same folder twice, which can
produce mismatched snapshots between children and rawChildren. Update the logic
in useFileBrowserActions.handleSync so listFolder(rootIpnsName, { forceResolve:
true }) performs the single resolve, then reuse that same FolderState result
instead of calling ensureFolderLoaded(..., { forceResolve: true }) again. Keep
the updates to updateFolderChildren, updateFolderRawChildren, and
updateFolderSequence sourced from the one resolved state.
In `@packages/sdk/src/__tests__/client-shared-write.test.ts`:
- Around line 132-143: The sharedFolder:updated tests are now only checking the
empty-array fallback instead of real resolved children, so update the fixtures
used in client-shared-write.test to produce resolver-openable children with
readKeySealed. Reuse the fixture-building pattern from folder-listing.test and
buildChildFixture (including sealNode/sealChildReadKey) so
sharedFolder:updated.children is asserted as actual ResolvedChild[] content
rather than [] in the affected test cases.
In `@packages/sdk/src/__tests__/client.test.ts`:
- Around line 252-261: The `folder:loaded.children` check in `client.test.ts` is
too weak because `mockChildren` is being soft-skipped, so it only verifies the
empty-skip path instead of real resolved payloads. Update the `folder:loaded`
assertion in the relevant test to use a properly resolved fixture, following the
`buildChildFixture` pattern from `folder-listing.test.ts`, and assert the actual
`ResolvedChild[]` content via `loadedEvent.children` rather than expecting an
empty array.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ffcd8adf-a864-4b53-821f-d74b058c7203
📒 Files selected for processing (131)
.planning/REQUIREMENTS.md.planning/ROADMAP.md.planning/STATE.md.planning/codebase/INTEGRATIONS.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-01-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-01-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-02-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-02-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-03-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-03-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-04-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-04-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-05-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-05-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-06-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-06-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-07-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-07-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-08-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-08-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-09-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-09-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-10-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-10-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-11-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-11-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-12-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-12-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-13-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-13-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-14-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-14-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-15-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-15-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-16-PLAN.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-16-SUMMARY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-CONTEXT.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-DISCUSSION-LOG.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-PATTERNS.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-RESEARCH.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-SECURITY.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-UAT.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-VALIDATION.md.planning/phases/68.2-sdk-owned-read-chain-and-resolved-folder-listings/68.2-VERIFICATION.md.planning/todos/completed/2026-07-06-d03-poll-invalidation-lacks-automated-coverage.md.planning/todos/pending/2026-07-06-68.2-coderabbit-hardening-backlog.md.planning/todos/pending/2026-07-06-d07-boundary-eslint-rule.md.planning/todos/pending/2026-07-06-gate-non-listing-read-facades.md.planning/todos/pending/2026-07-06-sharedfolderrow-drag-kind-classification.mdapps/web/src/components/file-browser/ContextMenu.tsxapps/web/src/components/file-browser/DetailsDialog.tsxapps/web/src/components/file-browser/FileBrowser.tsxapps/web/src/components/file-browser/FileList.tsxapps/web/src/components/file-browser/FileListItem.tsxapps/web/src/components/file-browser/MoveDialog.tsxapps/web/src/components/file-browser/ReplaceFileDialog.tsxapps/web/src/components/file-browser/SelectionActionBar.tsxapps/web/src/components/file-browser/SharedFileBrowser.tsxapps/web/src/components/file-browser/SharedFolderRow.tsxapps/web/src/components/file-browser/TextEditorDialog.tsxapps/web/src/components/file-browser/details/FileDetails.tsxapps/web/src/components/file-browser/details/FolderDetails.tsxapps/web/src/components/file-browser/details/VersionHistory.tsxapps/web/src/components/file-browser/useFileBrowserActions.tsapps/web/src/components/settings/ConnectionTest.tsxapps/web/src/components/settings/StorageTab.tsxapps/web/src/components/settings/VaultTab.tsxapps/web/src/hooks/__tests__/useSharedWriteOps.test.tsapps/web/src/hooks/__tests__/useSyncPolling.test.tsapps/web/src/hooks/folder-helpers.tsapps/web/src/hooks/shared-folder-projection.tsapps/web/src/hooks/useAuth.tsapps/web/src/hooks/useDropUpload.tsapps/web/src/hooks/useFileOperations.tsapps/web/src/hooks/useFileSize.tsapps/web/src/hooks/useFileVersions.tsapps/web/src/hooks/useFolderMutations.tsapps/web/src/hooks/useFolderNavigation.tsapps/web/src/hooks/useSharedNavigation.tsapps/web/src/hooks/useSharedNavigationActions.tsapps/web/src/hooks/useSharedWriteOps.tsapps/web/src/hooks/useStreamingPreview.tsapps/web/src/hooks/useSyncPolling.tsapps/web/src/lib/api/ipfs.tsapps/web/src/lib/clear-user-stores.tsapps/web/src/lib/crypto/key-wrapping.tsapps/web/src/lib/kind-cache.tsapps/web/src/lib/sdk-provider.tsapps/web/src/lib/version-transforms.tsapps/web/src/services/__tests__/ipns.service.test.tsapps/web/src/services/delete.service.test.tsapps/web/src/services/delete.service.tsapps/web/src/services/device-registry.service.tsapps/web/src/services/download.service.tsapps/web/src/services/index.tsapps/web/src/services/ipns.service.tsapps/web/src/services/search-index.service.tsapps/web/src/services/streaming-crypto.service.tsapps/web/src/services/upload.service.tsapps/web/src/services/vault-settings.service.tsapps/web/src/stores/__tests__/folder.store.test.tsapps/web/src/stores/folder.store.tsapps/web/src/stores/vault-settings.store.tsapps/web/src/utils/fileTypes.tsdocs/METADATA_SCHEMAS.mdpackages/core/src/__tests__/node-codec.test.tspackages/core/src/node/decode.tspackages/core/src/node/encode.tspackages/core/src/node/types.tspackages/sdk-core/src/folder/metadata-ops.tspackages/sdk/src/__tests__/byo-pinning-facade.test.tspackages/sdk/src/__tests__/client-shared-write.test.tspackages/sdk/src/__tests__/client.test.tspackages/sdk/src/__tests__/descend-shared-child.test.tspackages/sdk/src/__tests__/download-shared-file.test.tspackages/sdk/src/__tests__/folder-listing-gate.test.tspackages/sdk/src/__tests__/folder-listing.test.tspackages/sdk/src/__tests__/folder-metadata-facade.test.tspackages/sdk/src/__tests__/folder-reresolve.test.tspackages/sdk/src/__tests__/ipfs-transport-facade.test.tspackages/sdk/src/__tests__/resolve-child-identity.test.tspackages/sdk/src/__tests__/resolve-node-identity.test.tspackages/sdk/src/__tests__/resolve-share-root.test.tspackages/sdk/src/__tests__/vault-registry-facade.test.tspackages/sdk/src/client.tspackages/sdk/src/events.tspackages/sdk/src/folder-listing.tspackages/sdk/src/index.tspackages/sdk/src/share/shared-write.tstests/web-e2e/page-objects/file-browser/file-list.page.tstests/web-e2e/tests/shared-folder-desync.spec.ts
💤 Files with no reviewable changes (10)
- apps/web/src/lib/api/ipfs.ts
- apps/web/src/services/index.ts
- apps/web/src/hooks/useFileSize.ts
- apps/web/src/lib/clear-user-stores.ts
- apps/web/src/services/ipns.service.ts
- apps/web/src/services/tests/ipns.service.test.ts
- packages/sdk/src/share/shared-write.ts
- packages/core/src/tests/node-codec.test.ts
- packages/sdk-core/src/folder/metadata-ops.ts
- apps/web/src/lib/kind-cache.ts
Fix three in-scope findings from CodeRabbit's re-review of the fix commits; defer the rest as typed todos (freshness/perf changes too risky to land late in a verified-green phase). - DetailsDialog: derive the file/folder kind from the SDK-resolved parent listing (authoritative for un-visited subfolders) and fall back to folderStore membership only on a listing miss -- the prior folderStore-only heuristic misclassified an un-navigated folder as a file, rendering FileDetails and skipping folder-metadata resolution. - FileListItem: treat the `modifiedAt: 0` unresolved sentinel as the "—" placeholder instead of rendering the 1970 epoch. - STATE.md: sync the Phase 68.2 snapshot to 14 of 14 plans (matches ROADMAP). Deferred to todos: - resolveFileMetadata/downloadFromIpns ROT-07 gating -> gate-non-listing-read-facades - double forced re-resolve, shared-seed ordering, resolveChildren parallelism -> 68.2 CodeRabbit hardening backlog Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B7XhtqxJ7pAYkZTmdmKerE Entire-Checkpoint: fa4a41c813c7
Phase 68.2 — SDK-Owned Read Chain and Resolved Folder Listings
Moves the gated read chain and per-child metadata resolution entirely into
packages/sdk, exposes resolved folder listings (ResolvedChild), collapses the web store to a thin projection, and closes the Web/SDK folder-state desync bug class (SC#5).What changed
RotationHighWater.enforceResolved, fail-closed on unverified signature, parent-mirror generation sourcing) → IPFS fetch → node unseal, all insidepackages/sdk. RawresolveIpnsRecordis SDK-internal only. The web's parallel read services (ipns.service.ts,file-metadata.service.ts,kind-cache.ts,useFileSize.ts) are deleted.listFolder/listSharedFolderreturnResolvedChild[](ipnsName, name, kind, size?, modifiedAt, sequence), resolved once per folder load and cached in the SDK; the web file list, shared browser, and details dialogs render directly from it.folder.store.tsis a thinResolvedChild[]projection of SDK state; belt-and-suspenders freshness (re-resolve on navigation via{ forceResolve: true }+ poll-driveninvalidateOpenFolderfor the open folder) closes the desync class.SealedChildRefis reverted to its frozen NODE-03 five-field set.apps/web/srcmakes zero runtime calls into@cipherbox/sdk-core/@cipherbox/coreand no raw IPFS/IPNS access on either read or write path (type-only imports allowed), enforced by an allowlist-free grep gate.Gap closure
isFileRefon a bareSealedChildRefalways returned false, breaking download affordances and the download logic. Fixed with anisFileRefResolvedclassifier against the resolved listing.invalidateOpenFolder().Verification
68.2-VERIFICATION.md).68.2-VALIDATION.md).shared-folder-desync4/4,batch-download5/5,writable-shares29/29.tee-republish.test.ts(tee_key_state is empty— no tee-worker in the local stack); this phase touches no TEE/republish code, so they are infra-gated and covered by CI, not a regression. All read-chain / write-chain / publish-gate / share / file-ops / vault suites pass.68.2-SECURITY.md). ROT-07 read-path gate, fail-closed-on-unverified, parent-mirror generation, and D-07 boundary all verified in code.Deferred
SharedFolderRowdrag-payload kind classification uses the legacy path (functionally harmless — the shared drop handler ignoresDragItem.type; routes by ipnsName). Captured as a low-priority todo.Residual pre-existing 68.1 debt (GAP-1 resolveFileMetadata AEAD media/streaming, GAP-2 cold-reload DFS timeout, recovery) is out of this phase's scope and unchanged.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Bug Fixes