Skip to content

Offline-aware, conflict-safe document sync (timestamp merge + pending set)#57

Merged
TheAngryRaven merged 1 commit into
BETAfrom
claude/sync-timestamp-merge
May 25, 2026
Merged

Offline-aware, conflict-safe document sync (timestamp merge + pending set)#57
TheAngryRaven merged 1 commit into
BETAfrom
claude/sync-timestamp-merge

Conversation

@TheAngryRaven
Copy link
Copy Markdown
Owner

Summary

Makes the document auto-sync conflict-safe and offline-aware so it stops
"stomping" edits — directly addressing the timestamp-merge follow-up.

The merge model (you chose: pending-wins + timestamp LWW)

  • Every garage record carries updatedAt — added to Vehicle / VehicleType,
    and stamped in each storage save* (the sync write path writeOne keeps the
    cloud value, so a pull never re-stamps).
  • merge.ts (pure, unit-tested) — decideSync:
    • a pending local change (made offline / failed push) is priority-1
      pushed up, replacing the cloud copy, regardless of clocks;
    • otherwise last-write-wins by the record's own updatedAt (its logical edit
      time — never the server row time, so a late-uploaded stale edit can't win);
    • local-only → push (anon→account migration); cloud-only → pull.

Offline handling

  • pendingSync.ts — a persistent "pending changes" set in the plugin KV, so
    offline edits survive a reload.
  • autoSync tracks navigator.onLine + window online/offline. While
    offline (or on a failed push) a change is recorded pending; on reconnect /
    sign-in
    it flushes pending first (priority-1), then reconcileDocs does a
    timestamp-aware two-way merge of the rest (skipping pending keys).
  • StoragePanel flags offline state and shows the pending-change count.

Engine

  • reconcileDocs(userId, pendingKeys) replaces the old pushDocs/pullDocs
    (unconditional cloud-wins) with the merge above.

Logs are untouched (no auto-sync there yet, per scope). Manual push/pull in the
Cloud Sync panel is unchanged.

Test plan

  • npm run lint / npm run typecheck / npm run test:run (335; +7 merge cases) / npm run build
  • decideSync covered: pending-put push, pending-delete skip, local-only push, cloud-only pull, LWW both directions, equal/none skip
  • Live pass: edit a setup offline → "pending" flag; reconnect → it wins over a cloud copy; edit the same record on two devices → newer wins; anon local garage still migrates up on first sign-in

https://claude.ai/code/session_01K4mWVsXnwhtEi92FVBVhB3


Generated by Claude Code

… set)

Stops auto-sync from stomping edits and makes offline changes safe.

- Every garage record carries updatedAt: added to Vehicle/VehicleType; stamped
  in each storage save* (the sync write path keeps the cloud value).
- merge.ts (pure, tested): decideSync = pending-wins + last-write-wins by the
  record's own updatedAt (not the server row time).
- pendingSync.ts: persistent offline "pending changes" set in the plugin KV.
- syncEngine.reconcileDocs: timestamp-aware two-way merge (pull cloud-newer,
  push local-newer/-only), skipping pending keys; replaces pushDocs/pullDocs.
- autoSync: tracks navigator.onLine + window online/offline; records changes as
  pending when offline or on failed push; on reconnect/sign-in flushes pending
  first (priority-1, replacing cloud) then reconciles the rest.
- StoragePanel flags offline state + the pending-change count.

https://claude.ai/code/session_01K4mWVsXnwhtEi92FVBVhB3
@github-actions
Copy link
Copy Markdown

Coverage Summary

Lines: 14.57% (1548/10622) · Statements: 13.93% · Functions: 10.54% · Branches: 10.9%

Per-file coverage
File Lines Functions Branches
src/App.tsx 0% 0% 0%
src/components/AboutDialog.tsx 0% 0% 100%
src/components/admin/BannedIpsTab.tsx 0% 0% 0%
src/components/admin/CoursesTab.tsx 0% 0% 0%
src/components/admin/MessagesTab.tsx 0% 0% 0%
src/components/admin/SubmissionsTab.tsx 0% 0% 0%
src/components/admin/ToolsTab.tsx 0% 0% 0%
src/components/admin/TracksTab.tsx 0% 0% 0%
src/components/BrowserCompatDialog.tsx 0% 0% 0%
src/components/ContactDialog.tsx 0% 0% 0%
src/components/CreditsDialog.tsx 0% 0% 100%
src/components/DataloggerDownload.tsx 0% 0% 0%
src/components/drawer/DeviceSettingsTab.tsx 0% 0% 0%
src/components/drawer/DeviceTracksTab.tsx 0% 0% 0%
src/components/drawer/FilesTab.tsx 0% 0% 0%
src/components/drawer/KartsTab.tsx 0% 0% 0%
src/components/drawer/NotesTab.tsx 0% 0% 0%
src/components/drawer/SetupsTab.tsx 0% 0% 0%
src/components/drawer/TemplateCreator.tsx 0% 0% 0%
src/components/drawer/VehiclesTab.tsx 0% 0% 0%
src/components/ExternalRefBar.tsx 0% 0% 0%
src/components/FileImport.tsx 0% 0% 0%
src/components/FileManagerDrawer.tsx 0% 0% 0%
src/components/graphview/GraphPanel.tsx 0% 0% 0%
src/components/graphview/GraphViewPanel.tsx 0% 0% 0%
src/components/graphview/InfoBox.tsx 0% 0% 0%
src/components/graphview/MiniMap.tsx 0% 0% 0%
src/components/graphview/SingleSeriesChart.tsx 0% 0% 0%
src/components/InstallPrompt.tsx 0% 0% 0%
src/components/LandingPage.tsx 0% 0% 0%
src/components/LapSummaryWidget.tsx 0% 0% 0%
src/components/LapTable.tsx 0% 0% 0%
src/components/LocalWeatherDialog.tsx 0% 0% 0%
src/components/NavLink.tsx 0% 0% 0%
src/components/RaceLineView.tsx 0% 0% 0%
src/components/RangeSlider.tsx 0% 0% 0%
src/components/ResizableSplit.tsx 0% 0% 0%
src/components/SettingsModal.tsx 0% 0% 0%
src/components/SubmitTrackDialog.tsx 0% 0% 0%
src/components/SupportedFilesDialog.tsx 0% 0% 0%
src/components/tabs/CoachTab.tsx 0% 0% 100%
src/components/tabs/GraphViewTab.tsx 0% 0% 100%
src/components/tabs/LabsTab.tsx 0% 0% 100%
src/components/tabs/LapTimesTab.tsx 0% 0% 100%
src/components/tabs/ProfileTab.tsx 0% 0% 100%
src/components/tabs/RaceLineTab.tsx 0% 0% 0%
src/components/TelemetryChart.tsx 0% 0% 0%
src/components/track-editor/AddCourseDialog.tsx 0% 0% 0%
src/components/track-editor/AddTrackDialog.tsx 0% 0% 0%
src/components/track-editor/CourseForm.tsx 0% 0% 0%
src/components/track-editor/EditorModeToggle.tsx 0% 0% 0%
src/components/track-editor/VisualEditor.tsx 0% 0% 0%
src/components/TrackEditor.tsx 0% 0% 0%
src/components/TrackPromptDialog.tsx 0% 0% 0%
src/components/video-overlays/AnalogOverlay.tsx 0% 0% 0%
src/components/video-overlays/BarOverlay.tsx 0% 0% 0%
src/components/video-overlays/BubbleOverlay.tsx 0% 0% 0%
src/components/video-overlays/dataSourceResolver.ts 0% 0% 0%
src/components/video-overlays/DigitalOverlay.tsx 0% 0% 0%
src/components/video-overlays/GraphOverlay.tsx 0% 0% 0%
src/components/video-overlays/LapTimeOverlay.tsx 0% 0% 0%
src/components/video-overlays/MapOverlay.tsx 0% 0% 0%
src/components/video-overlays/OverlaySettingsPanel.tsx 0% 0% 0%
src/components/video-overlays/overlayUtils.ts 0% 0% 0%
src/components/video-overlays/PaceOverlay.tsx 0% 0% 0%
src/components/video-overlays/registry.ts 0% 0% 100%
src/components/video-overlays/SectorOverlay.tsx 0% 0% 0%
src/components/video-overlays/sectorUtils.ts 0% 0% 0%
src/components/video-overlays/themes.ts 0% 0% 0%
src/components/video-overlays/types.ts 0% 100% 100%
src/components/video-overlays/VideoExportDialog.tsx 0% 0% 0%
src/components/VideoPlayer.tsx 0% 0% 0%
src/components/WeatherPanel.tsx 0% 0% 0%
src/contexts/AuthContext.tsx 0% 0% 0%
src/contexts/DeviceContext.tsx 0% 0% 0%
src/contexts/SessionContext.tsx 0% 0% 0%
src/contexts/SettingsContext.tsx 0% 0% 0%
src/hooks/use-mobile.tsx 0% 0% 100%
src/hooks/use-toast.ts 0% 0% 0%
src/hooks/useAuth.ts 100% 100% 100%
src/hooks/useDataLoader.ts 0% 0% 0%
src/hooks/useDocumentHead.ts 0% 0% 0%
src/hooks/useFileManager.ts 0% 0% 0%
src/hooks/useKartManager.ts 100% 100% 100%
src/hooks/useLapManagement.ts 0% 0% 0%
src/hooks/useNoteManager.ts 0% 0% 0%
src/hooks/useOnlineStatus.ts 0% 0% 0%
src/hooks/usePlayback.ts 0% 0% 0%
src/hooks/useReferenceLap.ts 0% 0% 0%
src/hooks/useSessionData.ts 0% 0% 0%
src/hooks/useSessionMetadata.ts 0% 0% 0%
src/hooks/useSettings.ts 0% 0% 0%
src/hooks/useSetupManager.ts 0% 0% 100%
src/hooks/useTemplateManager.ts 0% 0% 0%
src/hooks/useTrackEditorForm.ts 0% 0% 0%
src/hooks/useVehicleManager.ts 0% 0% 100%
src/hooks/useVideoSync.ts 0% 0% 0%
src/integrations/lovable/index.ts 0% 0% 0%
src/lib/aimParser.ts 11.27% 18.18% 3.08%
src/lib/alfanoParser.ts 7.81% 40% 3.93%
src/lib/ble/test/mockBle.ts 100% 100% 50%
src/lib/ble/battery.ts 93.33% 100% 87.5%
src/lib/ble/connection.ts 0% 0% 0%
src/lib/ble/fileTransfer.ts 90.69% 95% 72.91%
src/lib/ble/format.ts 50% 100% 72.22%
src/lib/ble/index.ts 100% 100% 100%
src/lib/ble/internal.ts 100% 100% 50%
src/lib/ble/settings.ts 93.6% 100% 85.29%
src/lib/ble/trackSync.ts 89.69% 90.9% 70.96%
src/lib/ble/types.ts 100% 100% 100%
src/lib/bleDatalogger.ts 100% 100% 100%
src/lib/brakingZones.ts 0% 0% 0%
src/lib/browserCompat.ts 0% 0% 0%
src/lib/channels.ts 97.05% 90.9% 84.61%
src/lib/chartColors.ts 0% 0% 0%
src/lib/chartUtils.ts 0% 0% 0%
src/lib/courseDetection.ts 99.01% 100% 84.14%
src/lib/datalogParser.ts 18.51% 50% 17.39%
src/lib/db/index.ts 0% 0% 0%
src/lib/db/supabaseAdapter.ts 0% 0% 0%
src/lib/db/types.ts 100% 100% 100%
src/lib/dbUtils.ts 3.33% 0% 0%
src/lib/deviceSettingsSchema.ts 0% 0% 0%
src/lib/deviceTrackSync.ts 100% 100% 90%
src/lib/doveParser.ts 82.4% 72.72% 56.36%
src/lib/dovexParser.ts 76.56% 76.92% 47.27%
src/lib/fieldResolver.ts 0% 0% 0%
src/lib/fileStorage.ts 0% 0% 0%
src/lib/garageEvents.ts 0% 0% 100%
src/lib/gforceCalculation.ts 92.85% 100% 86.95%
src/lib/graphPrefsStorage.ts 0% 0% 0%
src/lib/kartStorage.ts 0% 0% 0%
src/lib/lapCalculation.ts 96.12% 100% 90.32%
src/lib/lapDelta.ts 98.96% 100% 82.35%
src/lib/motecParser.ts 4.29% 3.44% 0.69%
src/lib/nmeaParser.ts 84.43% 92.85% 67.62%
src/lib/noteStorage.ts 0% 0% 100%
src/lib/overlayCanvasRenderer.ts 0% 0% 0%
src/lib/parserUtils.ts 100% 100% 98.52%
src/lib/referenceUtils.ts 78.33% 72.72% 46.42%
src/lib/setupStorage.ts 0% 0% 0%
src/lib/speedBounds.ts 0% 0% 0%
src/lib/speedEvents.ts 0% 0% 0%
src/lib/templateStorage.ts 0% 0% 0%
src/lib/trackStorage.ts 0% 0% 0%
src/lib/trackUtils.ts 22.22% 12.5% 22.72%
src/lib/ubxParser.ts 5% 0% 0%
src/lib/utils.ts 0% 0% 100%
src/lib/vboParser.ts 2.4% 9.09% 2.43%
src/lib/vehicleStorage.ts 0% 0% 0%
src/lib/videoExport.ts 0% 0% 0%
src/lib/videoFileStorage.ts 0% 0% 0%
src/lib/videoStorage.ts 0% 0% 0%
src/lib/weatherService.ts 0% 0% 0%
src/pages/Admin.tsx 0% 0% 0%
src/pages/AuthCallback.tsx 0% 0% 0%
src/pages/ForgotPassword.tsx 0% 0% 0%
src/pages/Index.tsx 0% 0% 0%
src/pages/Login.tsx 0% 0% 0%
src/pages/NotFound.tsx 0% 0% 0%
src/pages/Privacy.tsx 0% 0% 0%
src/pages/Register.tsx 0% 0% 0%
src/pages/ResetPassword.tsx 0% 0% 0%
src/plugins/cloud-sync/autoSync.ts 0% 0% 0%
src/plugins/cloud-sync/cloudClient.ts 0% 0% 0%
src/plugins/cloud-sync/CloudFilesSection.tsx 0% 0% 0%
src/plugins/cloud-sync/CloudLogsPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/CloudSyncPanel.tsx 0% 0% 0%
src/plugins/cloud-sync/fileSync.ts 53.84% 27.27% 100%
src/plugins/cloud-sync/FileSyncToggle.tsx 0% 0% 0%
src/plugins/cloud-sync/index.ts 0% 0% 0%
src/plugins/cloud-sync/merge.ts 90.9% 66.66% 100%
src/plugins/cloud-sync/pendingSync.ts 0% 0% 0%
src/plugins/cloud-sync/profile.ts 0% 0% 0%
src/plugins/cloud-sync/StoragePanel.tsx 0% 0% 0%
src/plugins/cloud-sync/storageTypes.ts 100% 100% 100%
src/plugins/cloud-sync/syncEngine.ts 0% 0% 0%
src/plugins/cloud-sync/syncStores.ts 100% 100% 100%
src/plugins/index.ts 0% 0% 0%
src/plugins/mounts.ts 100% 100% 100%
src/plugins/panels.ts 100% 100% 100%
src/plugins/PluginMount.tsx 0% 0% 0%
src/plugins/PluginPanelHost.tsx 0% 0% 0%
src/plugins/registry.ts 100% 100% 100%
src/plugins/storage.ts 32.25% 8.33% 33.33%
src/plugins/types.ts 100% 100% 100%
src/types/racing.ts 100% 100% 100%

@TheAngryRaven TheAngryRaven merged commit fbb6ea7 into BETA May 25, 2026
5 checks passed
@TheAngryRaven TheAngryRaven deleted the claude/sync-timestamp-merge branch May 25, 2026 17:30
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