Skip to content

Per-file cloud sync + plugin framework primitives (storage + inline mounts)#45

Merged
TheAngryRaven merged 3 commits into
BETAfrom
claude/plugin-file-sync
May 24, 2026
Merged

Per-file cloud sync + plugin framework primitives (storage + inline mounts)#45
TheAngryRaven merged 3 commits into
BETAfrom
claude/plugin-file-sync

Conversation

@TheAngryRaven
Copy link
Copy Markdown
Owner

@TheAngryRaven TheAngryRaven commented May 24, 2026

Summary

Lets users individually choose which files sync to the cloud from the file manager (modeled on the device track/course sync UX), and adds the plugin-framework primitives that make it possible.

Per-file sync is opt-in, off by default; gated by VITE_ENABLE_CLOUD like the rest of cloud sync; fully offline-first (toggling offline records intent and uploads on reconnect).

Phase A — framework primitives (no behavior change on their own)

  • Plugin storage hook (plugins/storage.ts): getPluginStore(id) gives a plugin a schema-less KV store in its own IndexedDB DB (dove-plugin-<id>), fully decoupled from core dbUtils/DB_VERSION. Also surfaced as ctx.storage.
  • Inline mount framework (plugins/mounts.ts + PluginMount.tsx): sibling to the panel framework. Plugins contribute a PluginMountDef to a MountSlot; <PluginMount slot ctx> renders them error-boundaried + Suspense-wrapped, or nothing when no plugin targets the slot. FilesTab exposes MountSlot.FileRow (per file) and MountSlot.FileManagerSection (under the list).

Phase B — per-file cloud sync

  • cloud-sync/fileSync.ts: per-file selection state in the plugin's KV store + a pure, tested fileSyncStatus (offpendingsynced).
  • cloud-sync/FileSyncToggle.tsx: a FileRow mount — the per-file toggle (lazy chunk, ~1.3 kB).
  • syncEngine: pushAll uploads all garage docs but only the selected files; new pushFile for a single toggle; pulled files are marked synced.

Phase C — cloud-only files inline

  • cloud-sync/CloudFilesSection.tsx: a FileManagerSection mount listing files that are in your cloud but not on this device, each with a one-click pull. Pulling persists via ctx.onSaveFile (which refreshes the list), so the file moves into the list automatically.
  • syncEngine: listCloudFiles + downloadCloudFile; cloudOnlyNames pure helper (tested).
  • CloudSyncPanel copy updated to reflect selective push.

Deferred (follow-ups)

modified detection (file blobs are immutable so this is low-value for files) and a "sync all / select all" convenience.

Docs

plugins/README.md (storage + inline-mount authoring sections), CLAUDE.md, CHANGELOG.md.

Test plan

  • npm run lint, npm run typecheck, npm run test:run (303 passing), npm run build all green locally
  • New unit tests: getMounts selector; fileSyncStatus states; cloudOnlyNames
  • Manual (in a VITE_ENABLE_CLOUD=true build): toggle a file on while signed in → uploads + shows synced; toggle while offline → pending, uploads on reconnect; toggle off → stops syncing (cloud copy kept); a file synced from another device appears under "Available in cloud" and pulls into the list in one click

https://claude.ai/code/session_01QF56Xjp5ZMgXrqfTWD14Le

Foundational framework work for per-file cloud sync (and the plugin ecosystem
generally). No behavior change yet — the mount points render nothing until a
plugin contributes.

- storage.ts: getPluginStore(id) — schema-less KV scoped to a plugin in its own
  IndexedDB DB (dove-plugin-<id>), fully decoupled from core dbUtils/DB_VERSION.
  Also exposed as ctx.storage in PluginContext.
- mounts.ts + PluginMount.tsx: inline mount framework. Plugins contribute
  components to a MountSlot; <PluginMount slot ctx> renders them error-
  boundaried + Suspense-wrapped, or nothing when none. Sibling to the panel
  framework (cards) for injecting raw components into core UI.
- FilesTab wires two mount points: MountSlot.FileRow (per file) and
  MountSlot.FileManagerSection (under the list).
- Tests for the getMounts selector; registry test mock updated for ctx.storage.

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

github-actions Bot commented May 24, 2026

Coverage Summary

Lines: 14.56% (1493/10250) · Statements: 13.86% · Functions: 10.12% · Branches: 10.59%

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/GraphViewTab.tsx 0% 0% 100%
src/components/tabs/LabsTab.tsx 0% 0% 100%
src/components/tabs/LapTimesTab.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/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 17.3% 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/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/cloudClient.ts 0% 0% 100%
src/plugins/cloud-sync/CloudFilesSection.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/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%

Builds on the Phase A primitives. Files are now individually selectable for
sync, modeled on the device track-sync UX; opt-in, off by default.

- fileSync.ts: selection state in the plugin's own KV store (getPluginStore),
  with a pure, tested fileSyncStatus (off/pending/synced).
- FileSyncToggle.tsx: a FileRow mount — per-file toggle that selects + pushes
  (or records intent when offline/signed-out). Lazy-loaded (its own chunk).
- syncEngine: pushAll now uploads garage docs + only *selected* files; new
  pushFile for a single toggle; pulled files are marked synced.
- index.ts contributes the FileRow mount (lazy, cloud-gated) alongside the panel.
- Docs: plugins/README gains storage + inline-mount authoring sections;
  CLAUDE.md + CHANGELOG updated.

https://claude.ai/code/session_01QF56Xjp5ZMgXrqfTWD14Le
@TheAngryRaven TheAngryRaven changed the title Phase A: plugin storage hook + inline mount primitive Per-file cloud sync + plugin framework primitives (storage + inline mounts) May 24, 2026
Completes the per-file sync UX. Files that exist in the cloud but not on this
device are listed under the file manager (via the FileManagerSection mount),
each with a one-click pull. Pulling persists through ctx.onSaveFile, which
refreshes the list, so the file moves out of the cloud-only section into the
list automatically.

- syncEngine: listCloudFiles + downloadCloudFile.
- fileSync: cloudOnlyNames pure helper (+ tests).
- CloudFilesSection.tsx: the section mount (lazy, cloud-gated, signed-in only).
- CloudSyncPanel copy updated to reflect selective push.
- modified detection + "sync all" remain follow-ups.

https://claude.ai/code/session_01QF56Xjp5ZMgXrqfTWD14Le
@TheAngryRaven TheAngryRaven merged commit ac3084d into BETA May 24, 2026
5 checks passed
@TheAngryRaven TheAngryRaven deleted the claude/plugin-file-sync branch May 24, 2026 18:48
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