Skip to content

fix(web): update sequence numbers after single-item folder mutations#256

Merged
FSM1 merged 5 commits into
mainfrom
fix/folder-mutation-seq-tracking
Mar 3, 2026
Merged

fix(web): update sequence numbers after single-item folder mutations#256
FSM1 merged 5 commits into
mainfrom
fix/folder-mutation-seq-tracking

Conversation

@FSM1

@FSM1 FSM1 commented Mar 3, 2026

Copy link
Copy Markdown
Owner

Summary

  • Single-item folder operations (handleMove, handleRename, handleDelete) in useFolderMutations did not call updateFolderSequence after IPNS publishes, leaving stale sequence numbers in the Zustand store
  • The next operation on the same folder would send an outdated expectedSequenceNumber → 409 conflict that the UI swallowed silently (move dialog staying open, rename/delete appearing to succeed but leaving inconsistent state)
  • Updated all 6 service functions (moveFolder, moveFile, renameFolder, renameFile, deleteFolder, deleteFileFromFolder) to return new sequence numbers from their updateFolderMetadata calls
  • Updated all 3 hooks to capture and persist the returned sequences via store.updateFolderSequence()

Root cause of E2E failure: Test 4.13 (single delete) left the root folder's sequence stale → Test 5.1 (move file) hit 409 → handleMoveConfirm caught error silently → dialog stayed open → 15s timeout

Test plan

  • Web E2E full-workflow.spec.ts passes (specifically test 5.1 "Move file via context menu")
  • Web E2E sharing and invite-link workflow tests still pass
  • TypeCheck passes (pnpm typecheck)
  • Web build succeeds (pnpm --filter web build)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Centralized conflict-retry for folder/file operations and improved sequencing so create/rename/move/delete flows update local state reliably after server changes.
  • Bug Fixes
    • Better handling of conflicts with a visible sync indicator and a single automatic retry to reduce user-facing errors.
  • Tests
    • Hardened desktop e2e cleanup to avoid failures on certain mounts.

handleMove, handleRename, and handleDelete in useFolderMutations did not
call updateFolderSequence after successful IPNS publishes, leaving stale
sequence numbers in the Zustand store. The next operation on the same
folder would send an outdated expectedSequenceNumber, triggering a 409
conflict that the UI swallowed silently (e.g. move dialog staying open).

Changes:
- folder.service: moveFolder, moveFile, renameFolder, renameFile,
  deleteFolder, deleteFileFromFolder now return new sequence numbers
  from their updateFolderMetadata calls
- useFolderMutations: handleMove, handleRename, handleDelete capture
  returned sequences and call store.updateFolderSequence()

This aligns single-item operations with the batch operations
(handleMoveItems, handleDeleteItems) which already tracked sequences.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 18b82a4a029c
@coderabbitai

coderabbitai Bot commented Mar 3, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@FSM1 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 16 minutes and 53 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 852300c and 9cf711e.

📒 Files selected for processing (1)
  • apps/web/src/hooks/useFolderMutations.ts

Walkthrough

Centralized conflict-retry via a new withConflictRetry helper; folder/file service methods now return sequence-number metadata which mutation hooks consume to persist updated sequences to local store. File add/move/rename/delete flows updated to use the retry helper; public hook APIs unchanged. (49 words)

Changes

Cohort / File(s) Summary
Folder service signatures
apps/web/src/services/folder.service.ts
Return types expanded to include sequence-number fields (newSequenceNumber, destSequenceNumber, sourceSequenceNumber) for rename/move/delete/file operations; callers now capture and propagate those values.
Mutation hooks & helpers
apps/web/src/hooks/useFolderMutations.ts, apps/web/src/hooks/useFileOperations.ts, apps/web/src/hooks/folder-helpers.ts
Added withConflictRetry in folder-helpers.ts; mutation hooks replaced inline conflict-retry logic with withConflictRetry, and internal mutation helpers now consume returned sequence numbers to update local state. Public hook signatures unchanged.
E2E cleanup script
tests/e2e-desktop/scripts/test-conflict-detection.ps1
Replaced -ErrorAction SilentlyContinue with try/catch blocks around Remove-Item calls to avoid terminating exceptions on WinFSP mounts.
Manifest / ancillary
apps/web/... (manifest lines changed)
Manifest and ancillary diffs reflect the above logical and type changes (lines added/removed).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Hook as useFileOperations / useFolderMutations
    participant Retry as withConflictRetry
    participant Service as folder.service
    participant Resync as resyncFolder
    participant Store as LocalStore

    User->>Hook: trigger operation (create/rename/move/delete/add)
    Hook->>Retry: call perform operation via withConflictRetry
    Retry->>Service: perform operation (publish/update metadata)
    Service->>Store: publish/update (IPNS / metadata)
    Service-->>Retry: return { newSequenceNumber / destSequenceNumber / sourceSequenceNumber }
    Retry-->>Hook: return operation result
    Retry->>Resync: on 409 -> show sync, call resyncFolder, wait, then retry
    Hook->>Store: persist returned sequence numbers and updated children
    Hook-->>User: return result (public API unchanged)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly and clearly summarizes the main change: updating sequence numbers after single-item folder mutations.
Docstring Coverage ✅ Passed Docstring 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 unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/folder-mutation-seq-tracking

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

Copilot AI 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.

Pull request overview

Fixes stale IPNS sequence numbers in the web app’s Zustand folder store after single-item mutations, preventing follow-up operations from sending an outdated expectedSequenceNumber and hitting 409 conflicts.

Changes:

  • Updated single-item folder/file mutation service functions to return the new IPNS sequenceNumber from updateFolderMetadata.
  • Updated useFolderMutations single-item handlers (rename/move/delete) to persist returned sequence numbers via updateFolderSequence.
  • Adjusted move handlers to persist both source and destination sequence numbers after a move.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
apps/web/src/services/folder.service.ts Changes mutation APIs to return new sequence numbers from IPNS publishes.
apps/web/src/hooks/useFolderMutations.ts Captures returned sequence numbers and persists them to Zustand after rename/move/delete.

Comment thread apps/web/src/services/folder.service.ts
Comment thread apps/web/src/hooks/useFolderMutations.ts Outdated
Comment thread apps/web/src/hooks/useFolderMutations.ts Outdated
On WinFSP FUSE mounts, Remove-Item can throw terminating .NET exceptions
("The system cannot find the file specified") that -ErrorAction
SilentlyContinue does not suppress. This caused the conflict detection
test suite to report 1 failure even though all actual tests passed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 1cbe792ba670

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
tests/e2e-desktop/scripts/test-conflict-detection.ps1 (1)

232-234: Avoid fully silent catches in cleanup to keep CI failures diagnosable.

Line 232, Line 233, and Line 234 swallow all exceptions. Consider logging a low-noise warning/verbose message in each catch so unexpected mount/runtime issues are still visible without failing cleanup.

Proposed tweak
-try { Remove-Item -Path "$MountPoint\conflict-test-1.txt" -Force -ErrorAction SilentlyContinue } catch {}
-try { Remove-Item -Path "$MountPoint\conflict-test-2.txt" -Force -ErrorAction SilentlyContinue } catch {}
-try { Remove-Item -Path "$MountPoint\conflict-dir" -Recurse -Force -ErrorAction SilentlyContinue } catch {}
+try { Remove-Item -Path "$MountPoint\conflict-test-1.txt" -Force -ErrorAction SilentlyContinue } catch { Write-Verbose "Cleanup skipped conflict-test-1.txt: $_" }
+try { Remove-Item -Path "$MountPoint\conflict-test-2.txt" -Force -ErrorAction SilentlyContinue } catch { Write-Verbose "Cleanup skipped conflict-test-2.txt: $_" }
+try { Remove-Item -Path "$MountPoint\conflict-dir" -Recurse -Force -ErrorAction SilentlyContinue } catch { Write-Verbose "Cleanup skipped conflict-dir: $_" }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/e2e-desktop/scripts/test-conflict-detection.ps1` around lines 232 -
234, The three try/catch cleanup blocks that call Remove-Item for
"$MountPoint\conflict-test-1.txt", "$MountPoint\conflict-test-2.txt" and
"$MountPoint\conflict-dir" currently swallow all exceptions; change each catch
to emit a low-noise diagnostic (e.g., Write-Verbose or Write-Warning) that
includes the target path and the caught exception ($_ or $PSItem) so CI logs
show unexpected mount/runtime errors while still not failing cleanup, keeping
Remove-Item and the same -Force/-Recurse/-ErrorAction usage intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/e2e-desktop/scripts/test-conflict-detection.ps1`:
- Around line 232-234: The three try/catch cleanup blocks that call Remove-Item
for "$MountPoint\conflict-test-1.txt", "$MountPoint\conflict-test-2.txt" and
"$MountPoint\conflict-dir" currently swallow all exceptions; change each catch
to emit a low-noise diagnostic (e.g., Write-Verbose or Write-Warning) that
includes the target path and the caught exception ($_ or $PSItem) so CI logs
show unexpected mount/runtime errors while still not failing cleanup, keeping
Remove-Item and the same -Force/-Recurse/-ErrorAction usage intact.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddc7889 and 50b0d2b.

📒 Files selected for processing (1)
  • tests/e2e-desktop/scripts/test-conflict-detection.ps1

… moves

moveFolder() and moveFile() used the stale pre-increment sequence number
for the second updateFolderMetadata call when moving within the same
folder, which would trigger a spurious 409 conflict. The batch variant
(handleMoveItems) already had this guard. Align single-item variants.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 62a37c394b40
- Extract `withConflictRetry<T>()` into folder-helpers.ts, replacing
  8 copy-pasted conflict-retry scaffolds across useFolderMutations and
  useFileOperations (-89 lines net)
- Remove unused `unpinCid` parameter from deleteFolder service
- Propagate orphaned file IPNS names through handleDelete and
  fire-and-forget resolve+unpin their metadata CIDs
- Unify handleCreate to use performParentUpdate for both initial
  attempt and retry (was using different code paths)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: 2f940c493c11
@codecov

codecov Bot commented Mar 3, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 46.68%. Comparing base (ef90514) to head (9cf711e).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #256      +/-   ##
==========================================
+ Coverage   46.31%   46.68%   +0.37%     
==========================================
  Files         106      106              
  Lines        8266     8255      -11     
  Branches      591      594       +3     
==========================================
+ Hits         3828     3854      +26     
+ Misses       4271     4233      -38     
- Partials      167      168       +1     
Flag Coverage Δ
api 84.55% <ø> (+0.68%) ⬆️
crypto 84.55% <ø> (+0.68%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.
see 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI 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.

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

apps/web/src/hooks/useFolderMutations.ts:636

  • If deleteFolder drops the unused unpinCid parameter (it’s not used in the service), this call site should also stop passing unpinFromIpfs to avoid carrying dead arguments through the stack.
   * Delete multiple files/folders in a single IPNS publish.
   *
   * Removes all items from parent's children array, collects CIDs to unpin,
   * then publishes metadata and IPNS once for the entire batch.
   *
   * @param items - Array of { id, type } to delete

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/hooks/useFolderMutations.ts`:
- Around line 611-618: The fire-and-forget loop calls
resolveIpnsRecord(...).then(...) and invokes unpinFromIpfs(resolved.cid) without
returning or catching its Promise, which can create unhandled rejections; update
the then handler in the loop that iterates deleteResult.orphanedIpnsNames so it
returns or explicitly catches the Promise from unpinFromIpfs (e.g., return
unpinFromIpfs(resolved.cid).catch(() => {}) or await inside an async then and
handle errors) so any rejection from unpinFromIpfs is observed and
swallowed/handled; ensure you reference resolveIpnsRecord, unpinFromIpfs, and
deleteResult.orphanedIpnsNames when making the change.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 50b0d2b and 852300c.

📒 Files selected for processing (4)
  • apps/web/src/hooks/folder-helpers.ts
  • apps/web/src/hooks/useFileOperations.ts
  • apps/web/src/hooks/useFolderMutations.ts
  • apps/web/src/services/folder.service.ts

Comment thread apps/web/src/hooks/useFolderMutations.ts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Entire-Checkpoint: f5855bbc496a
@FSM1 FSM1 enabled auto-merge (squash) March 3, 2026 19:22
@FSM1 FSM1 merged commit e7e8f5f into main Mar 3, 2026
19 checks passed
@FSM1 FSM1 deleted the fix/folder-mutation-seq-tracking branch March 4, 2026 01:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants