Skip to content

fix: prevent deleted toolbar bookmarks from being reverted by auto-sync#6

Merged
ralyodio merged 5 commits intomasterfrom
fix/toolbar-delete-revert-race-condition
Mar 7, 2026
Merged

fix: prevent deleted toolbar bookmarks from being reverted by auto-sync#6
ralyodio merged 5 commits intomasterfrom
fix/toolbar-delete-revert-race-condition

Conversation

@Preshy
Copy link
Copy Markdown
Contributor

@Preshy Preshy commented Mar 7, 2026

Summary

Fixes a race condition where deleting a bookmark from the toolbar gets reverted when auto-sync runs.

Root Cause

A tombstone overwrite race condition in performSync():

  1. performSync reads localTombstones = [] at sync start
  2. User deletes a bookmark → onRemoved fires → addTombstone() writes new tombstone to storage
  3. performSync computes mergedTombstones from the stale tombstones read in step 1
  4. performSync calls storeTombstones(mergedTombstones)overwrites the new tombstone
  5. Follow-up sync has no tombstone → re-adds bookmark from cloud → deletion reverted

Fix

Before writing merged tombstones, re-read current tombstones from storage to preserve any tombstones written concurrently by onRemoved:

const mergedTombstones = mergeTombstonesLocal(localTombstones, cloudTombstones);
const currentTombstones = await getTombstones();
const safeMergedTombstones = mergeTombstonesLocal(currentTombstones, mergedTombstones);
await storeTombstones(safeMergedTombstones);

Tests

Unit tests (19 tests) — toolbar-delete-revert.test.js

  • Tombstone filtering, full sync flow, race condition, server-side push, cross-browser paths, edge cases
  • Uses copied functions for isolated unit testing

Integration tests (10 tests) — toolbar-delete-race-integration.test.js

  • Calls the REAL performSync code (not copied/reimplemented functions) with fully mocked browser.* and fetch APIs
  • Simulates the race condition: delays cloud GET, fires the real onRemoved handler mid-sync, verifies the tombstone is preserved and the bookmark isn't re-added
  • Tests: basic sync flow, tombstone preservation during race, tombstone merge correctness, categorization filtering, concurrent sync guard, state management

Test-only exports (__test__ in background/index.js)

  • Guarded behind import.meta.env?.VITEST — tree-shaken in production builds
  • Exports performSync, addTombstone, categorizeCloudBookmarks, resetState, etc.
  • No impact on production bundle size (verified: Chrome build 54.64 kB unchanged)

Results

  • 527 tests pass (517 pre-existing + 10 new integration tests)
  • 61 pre-existing failures in store.test.js (unrelated storage.setItem issue)
  • 0 regressions

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a race condition where deleting a bookmark from the toolbar gets reverted by auto-sync. The root cause was that performSync() read tombstones at the start of the sync, but if a user deleted a bookmark mid-sync, the onRemoved handler wrote a new tombstone to storage that would be overwritten when performSync later called storeTombstones() with its stale merge result.

Changes:

  • Re-reads current tombstones from storage before writing merged tombstones in performSync(), preserving any concurrent tombstones written by onRemoved during the sync.
  • Adds comprehensive unit tests (toolbar-delete-revert.test.js) covering tombstone filtering, race condition scenarios, and cross-browser edge cases using copied functions.
  • Adds integration tests (toolbar-delete-race-integration.test.js) that call the real performSync code with mocked browser APIs and simulates the exact race condition.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
apps/extension/src/background/index.js Re-reads tombstones before writing to avoid overwriting concurrent tombstones; updates all downstream references to use safeMergedTombstones; adds __test__ exports guarded by VITEST for integration testing
apps/extension/__tests__/toolbar-delete-revert.test.js Unit tests for tombstone filtering, full sync flow simulation, race condition demonstration, server-side push, and edge cases using copied helper functions
apps/extension/__tests__/toolbar-delete-race-integration.test.js Integration tests calling real performSync with mocked browser/fetch APIs, simulating the race condition by blocking the cloud GET and firing onRemoved mid-sync

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/extension/__tests__/toolbar-delete-revert.test.js Outdated
Preshy and others added 5 commits March 7, 2026 14:38
Race condition: when a user deletes a bookmark while a sync is running,
performSync's storeTombstones() overwrites the tombstone created by the
onRemoved listener, causing the follow-up sync to re-add the bookmark
from cloud.

Fix: re-read current tombstones from storage before writing to preserve
any tombstones added concurrently during the sync.
…er APIs

Add __test__ exports (VITEST-only, tree-shaken in production) to
background/index.js so integration tests can import and call the real
performSync, addTombstone, categorizeCloudBookmarks, etc.

New test file: toolbar-delete-race-integration.test.js (10 tests)
- Calls the REAL performSync with fully mocked browser.* and fetch APIs
- Simulates the tombstone race condition: delays cloud GET, fires
  onRemoved mid-sync, verifies tombstone is preserved after sync
- Verifies deleted bookmark is NOT re-added from cloud
- Tests tombstone merge, categorization filtering, concurrent sync
  guard, and state management
- All 10 tests pass; 527 total passing (was 517)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Preshy Preshy force-pushed the fix/toolbar-delete-revert-race-condition branch from f416513 to e31f363 Compare March 7, 2026 13:38
@ralyodio ralyodio merged commit dc3023d into master Mar 7, 2026
4 of 6 checks passed
@ralyodio ralyodio deleted the fix/toolbar-delete-revert-race-condition branch March 7, 2026 13:40
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.

3 participants