From 907ab4e267d16eced888a4e558e8ec2d31311ae6 Mon Sep 17 00:00:00 2001 From: James Pine Date: Sun, 29 Mar 2026 03:24:53 -0700 Subject: [PATCH] Add archive sources UI with full Rust integration - Create core/src/data/ module with SourceManager wrapping sd-archive Engine - Add Sources to GroupType and Source to ItemType enums - Add default Sources group to new library creation - Register source operations: create, list, get, delete, sync, list_items - Register adapter operations: list, config, update - Add bundled adapter sync from workspace adapters/ directory - Add adapter update system with BLAKE3 change detection and backup/rollback - Frontend: Sources home, source detail with virtualized list, adapters screen - Frontend: SourcesGroup sidebar, SpaceGroup dispatch, spaceItemUtils - Frontend: TopBar integration (path bar, search, sync, actions menu) - Frontend: Tab title sync, adapter icon lookup hook - Regenerate TypeScript types Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/mobile/ios/Podfile.lock | 400 +++++++-------- .../ios/Spacedrive.xcodeproj/project.pbxproj | 4 +- core/src/data/manager.rs | 219 ++++++++ core/src/data/mod.rs | 5 + core/src/domain/space.rs | 9 + core/src/library/manager.rs | 39 ++ core/src/ops/adapters/config/mod.rs | 5 + core/src/ops/adapters/config/query.rs | 91 ++++ core/src/ops/adapters/list/mod.rs | 5 + core/src/ops/adapters/list/query.rs | 83 +++ core/src/ops/adapters/mod.rs | 9 + core/src/ops/adapters/update/action.rs | 73 +++ core/src/ops/adapters/update/mod.rs | 5 + core/src/ops/mod.rs | 1 + core/src/ops/sources/delete/action.rs | 66 +++ core/src/ops/sources/delete/mod.rs | 5 + core/src/ops/sources/get/mod.rs | 5 + core/src/ops/sources/get/query.rs | 90 ++++ core/src/ops/sources/list_items/mod.rs | 5 + core/src/ops/sources/list_items/query.rs | 94 ++++ core/src/ops/sources/mod.rs | 8 + core/src/ops/sources/sync/action.rs | 74 +++ core/src/ops/sources/sync/mod.rs | 5 + crates/archive/src/engine.rs | 138 ++++- .../src/Spacebot/SpacebotContext.tsx | 8 +- .../interface/src/Spacebot/SpacebotLayout.tsx | 6 +- .../src/Spacebot/routes/TasksRoute.tsx | 2 +- .../src/components/Sources/SourceCard.tsx | 66 +++ .../src/components/Sources/SourceDataRow.tsx | 55 ++ .../src/components/Sources/SourcePathBar.tsx | 39 ++ .../components/Sources/SourceStatusBadge.tsx | 20 + .../src/components/Sources/SourceTypeIcon.tsx | 46 ++ .../components/SpacesSidebar/SourcesGroup.tsx | 80 +++ .../SpacesSidebar/SpaceCustomizationPanel.tsx | 4 + .../components/SpacesSidebar/SpaceGroup.tsx | 15 + .../SpacesSidebar/hooks/spaceItemUtils.ts | 20 + .../TabManager/TabNavigationSync.tsx | 14 +- .../interface/src/hooks/useAdapterIcons.ts | 23 + packages/interface/src/router.tsx | 15 + .../interface/src/routes/sources/Adapters.tsx | 383 ++++++++++++++ .../src/routes/sources/SourceDetail.tsx | 484 ++++++++++++++++++ .../interface/src/routes/sources/index.tsx | 98 ++++ packages/ts-client/src/generated/types.ts | 139 ++++- 43 files changed, 2737 insertions(+), 218 deletions(-) create mode 100644 core/src/data/manager.rs create mode 100644 core/src/data/mod.rs create mode 100644 core/src/ops/adapters/config/mod.rs create mode 100644 core/src/ops/adapters/config/query.rs create mode 100644 core/src/ops/adapters/list/mod.rs create mode 100644 core/src/ops/adapters/list/query.rs create mode 100644 core/src/ops/adapters/mod.rs create mode 100644 core/src/ops/adapters/update/action.rs create mode 100644 core/src/ops/adapters/update/mod.rs create mode 100644 core/src/ops/sources/delete/action.rs create mode 100644 core/src/ops/sources/delete/mod.rs create mode 100644 core/src/ops/sources/get/mod.rs create mode 100644 core/src/ops/sources/get/query.rs create mode 100644 core/src/ops/sources/list_items/mod.rs create mode 100644 core/src/ops/sources/list_items/query.rs create mode 100644 core/src/ops/sources/sync/action.rs create mode 100644 core/src/ops/sources/sync/mod.rs create mode 100644 packages/interface/src/components/Sources/SourceCard.tsx create mode 100644 packages/interface/src/components/Sources/SourceDataRow.tsx create mode 100644 packages/interface/src/components/Sources/SourcePathBar.tsx create mode 100644 packages/interface/src/components/Sources/SourceStatusBadge.tsx create mode 100644 packages/interface/src/components/Sources/SourceTypeIcon.tsx create mode 100644 packages/interface/src/components/SpacesSidebar/SourcesGroup.tsx create mode 100644 packages/interface/src/hooks/useAdapterIcons.ts create mode 100644 packages/interface/src/routes/sources/Adapters.tsx create mode 100644 packages/interface/src/routes/sources/SourceDetail.tsx create mode 100644 packages/interface/src/routes/sources/index.tsx diff --git a/apps/mobile/ios/Podfile.lock b/apps/mobile/ios/Podfile.lock index 04f3fc240..bfcaf1eaa 100644 --- a/apps/mobile/ios/Podfile.lock +++ b/apps/mobile/ios/Podfile.lock @@ -261,8 +261,8 @@ PODS: - hermes-engine (0.81.5): - hermes-engine/Pre-built (= 0.81.5) - hermes-engine/Pre-built (0.81.5) - - libavif/core (0.11.1) - - libavif/libdav1d (0.11.1): + - libavif/core (1.0.0) + - libavif/libdav1d (1.0.0): - libavif/core - libdav1d (>= 0.6.0) - libdav1d (1.2.0) @@ -2348,9 +2348,9 @@ PODS: - Yoga - SDMobileCore (1.0.0): - ExpoModulesCore - - SDWebImage (5.21.5): - - SDWebImage/Core (= 5.21.5) - - SDWebImage/Core (5.21.5) + - SDWebImage (5.21.7): + - SDWebImage/Core (= 5.21.7) + - SDWebImage/Core (5.21.7) - SDWebImageAVIFCoder (0.11.1): - libavif/core (>= 0.11.0) - SDWebImage (~> 5.10) @@ -2367,107 +2367,107 @@ PODS: - ZXingObjC/Core DEPENDENCIES: - - "EXConstants (from `../../../node_modules/.bun/expo-constants@18.0.11+9bddab83625b4ca7/node_modules/expo-constants/ios`)" + - "EXConstants (from `../../../node_modules/.bun/expo-constants@18.0.11+3ee4ad13fefe912b/node_modules/expo-constants/ios`)" - "EXJSONUtils (from `../../../node_modules/.bun/expo-json-utils@0.15.0/node_modules/expo-json-utils/ios`)" - - "EXManifests (from `../../../node_modules/.bun/expo-manifests@1.0.10+9bddab83625b4ca7/node_modules/expo-manifests/ios`)" - - "Expo (from `../../../node_modules/.bun/expo@54.0.27+9bddab83625b4ca7/node_modules/expo`)" - - "expo-dev-client (from `../../../node_modules/.bun/expo-dev-client@6.0.20+9bddab83625b4ca7/node_modules/expo-dev-client/ios`)" - - "expo-dev-launcher (from `../../../node_modules/.bun/expo-dev-launcher@6.0.20+9bddab83625b4ca7/node_modules/expo-dev-launcher`)" - - "expo-dev-menu (from `../../../node_modules/.bun/expo-dev-menu@7.0.18+9bddab83625b4ca7/node_modules/expo-dev-menu`)" - - "expo-dev-menu-interface (from `../../../node_modules/.bun/expo-dev-menu-interface@2.0.0+9bddab83625b4ca7/node_modules/expo-dev-menu-interface/ios`)" - - "ExpoAsset (from `../../../node_modules/.bun/expo-asset@12.0.11+9bddab83625b4ca7/node_modules/expo-asset/ios`)" - - "ExpoBlur (from `../../../node_modules/.bun/expo-blur@15.0.8+9bddab83625b4ca7/node_modules/expo-blur/ios`)" - - "ExpoCamera (from `../../../node_modules/.bun/expo-camera@17.0.10+9bddab83625b4ca7/node_modules/expo-camera/ios`)" - - "ExpoDocumentPicker (from `../../../node_modules/.bun/expo-document-picker@14.0.8+9bddab83625b4ca7/node_modules/expo-document-picker/ios`)" - - "ExpoFileSystem (from `../../../node_modules/.bun/expo-file-system@19.0.20+9bddab83625b4ca7/node_modules/expo-file-system/ios`)" - - "ExpoFont (from `../../../node_modules/.bun/expo-font@14.0.10+9915544d233ac918/node_modules/expo-font/ios`)" - - "ExpoHaptics (from `../../../node_modules/.bun/expo-haptics@15.0.8+9bddab83625b4ca7/node_modules/expo-haptics/ios`)" - - "ExpoHead (from `../../../node_modules/.bun/expo-router@6.0.17+74a3cf8ce2a4bc65/node_modules/expo-router/ios`)" - - "ExpoImage (from `../../../node_modules/.bun/expo-image@3.0.11+9bddab83625b4ca7/node_modules/expo-image/ios`)" + - "EXManifests (from `../../../node_modules/.bun/expo-manifests@1.0.10+3ee4ad13fefe912b/node_modules/expo-manifests/ios`)" + - "Expo (from `../../../node_modules/.bun/expo@54.0.27+3ee4ad13fefe912b/node_modules/expo`)" + - "expo-dev-client (from `../../../node_modules/.bun/expo-dev-client@6.0.20+3ee4ad13fefe912b/node_modules/expo-dev-client/ios`)" + - "expo-dev-launcher (from `../../../node_modules/.bun/expo-dev-launcher@6.0.20+3ee4ad13fefe912b/node_modules/expo-dev-launcher`)" + - "expo-dev-menu (from `../../../node_modules/.bun/expo-dev-menu@7.0.18+3ee4ad13fefe912b/node_modules/expo-dev-menu`)" + - "expo-dev-menu-interface (from `../../../node_modules/.bun/expo-dev-menu-interface@2.0.0+3ee4ad13fefe912b/node_modules/expo-dev-menu-interface/ios`)" + - "ExpoAsset (from `../../../node_modules/.bun/expo-asset@12.0.11+3ee4ad13fefe912b/node_modules/expo-asset/ios`)" + - "ExpoBlur (from `../../../node_modules/.bun/expo-blur@15.0.8+3ee4ad13fefe912b/node_modules/expo-blur/ios`)" + - "ExpoCamera (from `../../../node_modules/.bun/expo-camera@17.0.10+3ee4ad13fefe912b/node_modules/expo-camera/ios`)" + - "ExpoDocumentPicker (from `../../../node_modules/.bun/expo-document-picker@14.0.8+3ee4ad13fefe912b/node_modules/expo-document-picker/ios`)" + - "ExpoFileSystem (from `../../../node_modules/.bun/expo-file-system@19.0.20+3ee4ad13fefe912b/node_modules/expo-file-system/ios`)" + - "ExpoFont (from `../../../node_modules/.bun/expo-font@14.0.10+c262bee79918334c/node_modules/expo-font/ios`)" + - "ExpoHaptics (from `../../../node_modules/.bun/expo-haptics@15.0.8+3ee4ad13fefe912b/node_modules/expo-haptics/ios`)" + - "ExpoHead (from `../../../node_modules/.bun/expo-router@6.0.17+7dc14032edcce378/node_modules/expo-router/ios`)" + - "ExpoImage (from `../../../node_modules/.bun/expo-image@3.0.11+3ee4ad13fefe912b/node_modules/expo-image/ios`)" - "ExpoKeepAwake (from `../../../node_modules/.bun/expo-keep-awake@15.0.8+ddb0696906414ead/node_modules/expo-keep-awake/ios`)" - - "ExpoLinking (from `../../../node_modules/.bun/expo-linking@8.0.10+9bddab83625b4ca7/node_modules/expo-linking/ios`)" - - "ExpoModulesCore (from `../../../node_modules/.bun/expo-modules-core@3.0.28+c1b4cdd6e4cb2f11/node_modules/expo-modules-core`)" - - "ExpoSplashScreen (from `../../../node_modules/.bun/expo-splash-screen@31.0.12+9bddab83625b4ca7/node_modules/expo-splash-screen/ios`)" - - "EXUpdatesInterface (from `../../../node_modules/.bun/expo-updates-interface@2.0.0+9bddab83625b4ca7/node_modules/expo-updates-interface/ios`)" - - "FBLazyVector (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/FBLazyVector`)" - - "hermes-engine (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)" - - "LiquidGlass (from `../../../node_modules/.bun/@callstack+liquid-glass@0.7.0+c1b4cdd6e4cb2f11/node_modules/@callstack/liquid-glass`)" - - "RCTDeprecation (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)" - - "RCTRequired (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Required`)" - - "RCTTypeSafety (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/TypeSafety`)" - - "React (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/`)" - - "React-callinvoker (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/callinvoker`)" - - "React-Core (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/`)" - - "React-Core-prebuilt (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React-Core-prebuilt.podspec`)" - - "React-Core/RCTWebSocket (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/`)" - - "React-CoreModules (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React/CoreModules`)" - - "React-cxxreact (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/cxxreact`)" - - "React-debug (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/debug`)" - - "React-defaultsnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/defaults`)" - - "React-domnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/dom`)" - - "React-Fabric (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon`)" - - "React-FabricComponents (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon`)" - - "React-FabricImage (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon`)" - - "React-featureflags (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/featureflags`)" - - "React-featureflagsnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/featureflags`)" - - "React-graphics (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/graphics`)" - - "React-hermes (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/hermes`)" - - "React-idlecallbacksnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`)" - - "React-ImageManager (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`)" - - "React-jserrorhandler (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jserrorhandler`)" - - "React-jsi (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsi`)" - - "React-jsiexecutor (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsiexecutor`)" - - "React-jsinspector (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsinspector-modern`)" - - "React-jsinspectorcdp (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsinspector-modern/cdp`)" - - "React-jsinspectornetwork (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsinspector-modern/network`)" - - "React-jsinspectortracing (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsinspector-modern/tracing`)" - - "React-jsitooling (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsitooling`)" - - "React-jsitracing (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/hermes/executor/`)" - - "React-logger (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/logger`)" - - "React-Mapbuffer (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon`)" - - "React-microtasksnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)" - - "react-native-safe-area-context (from `../../../node_modules/.bun/react-native-safe-area-context@5.6.2+c1b4cdd6e4cb2f11/node_modules/react-native-safe-area-context`)" + - "ExpoLinking (from `../../../node_modules/.bun/expo-linking@8.0.10+3ee4ad13fefe912b/node_modules/expo-linking/ios`)" + - "ExpoModulesCore (from `../../../node_modules/.bun/expo-modules-core@3.0.28+87dd5a4c738f4c73/node_modules/expo-modules-core`)" + - "ExpoSplashScreen (from `../../../node_modules/.bun/expo-splash-screen@31.0.12+3ee4ad13fefe912b/node_modules/expo-splash-screen/ios`)" + - "EXUpdatesInterface (from `../../../node_modules/.bun/expo-updates-interface@2.0.0+3ee4ad13fefe912b/node_modules/expo-updates-interface/ios`)" + - "FBLazyVector (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/FBLazyVector`)" + - "hermes-engine (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)" + - "LiquidGlass (from `../../../node_modules/.bun/@callstack+liquid-glass@0.7.0+87dd5a4c738f4c73/node_modules/@callstack/liquid-glass`)" + - "RCTDeprecation (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`)" + - "RCTRequired (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Required`)" + - "RCTTypeSafety (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/TypeSafety`)" + - "React (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/`)" + - "React-callinvoker (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/callinvoker`)" + - "React-Core (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/`)" + - "React-Core-prebuilt (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React-Core-prebuilt.podspec`)" + - "React-Core/RCTWebSocket (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/`)" + - "React-CoreModules (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React/CoreModules`)" + - "React-cxxreact (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/cxxreact`)" + - "React-debug (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/debug`)" + - "React-defaultsnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/defaults`)" + - "React-domnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/dom`)" + - "React-Fabric (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon`)" + - "React-FabricComponents (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon`)" + - "React-FabricImage (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon`)" + - "React-featureflags (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/featureflags`)" + - "React-featureflagsnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/featureflags`)" + - "React-graphics (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/graphics`)" + - "React-hermes (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/hermes`)" + - "React-idlecallbacksnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`)" + - "React-ImageManager (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`)" + - "React-jserrorhandler (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jserrorhandler`)" + - "React-jsi (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsi`)" + - "React-jsiexecutor (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsiexecutor`)" + - "React-jsinspector (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsinspector-modern`)" + - "React-jsinspectorcdp (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsinspector-modern/cdp`)" + - "React-jsinspectornetwork (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsinspector-modern/network`)" + - "React-jsinspectortracing (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsinspector-modern/tracing`)" + - "React-jsitooling (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsitooling`)" + - "React-jsitracing (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/hermes/executor/`)" + - "React-logger (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/logger`)" + - "React-Mapbuffer (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon`)" + - "React-microtasksnativemodule (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/microtasks`)" + - "react-native-safe-area-context (from `../../../node_modules/.bun/react-native-safe-area-context@5.6.2+87dd5a4c738f4c73/node_modules/react-native-safe-area-context`)" - "react-native-slider (from `../../../node_modules/.bun/@react-native-community+slider@5.1.1/node_modules/@react-native-community/slider`)" - - "React-NativeModulesApple (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)" - - "React-oscompat (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/oscompat`)" - - "React-perflogger (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/reactperflogger`)" - - "React-performancetimeline (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/performance/timeline`)" - - "React-RCTActionSheet (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/ActionSheetIOS`)" - - "React-RCTAnimation (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/NativeAnimation`)" - - "React-RCTAppDelegate (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/AppDelegate`)" - - "React-RCTBlob (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Blob`)" - - "React-RCTFabric (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React`)" - - "React-RCTFBReactNativeSpec (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React`)" - - "React-RCTImage (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Image`)" - - "React-RCTLinking (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/LinkingIOS`)" - - "React-RCTNetwork (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Network`)" - - "React-RCTRuntime (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React/Runtime`)" - - "React-RCTSettings (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Settings`)" - - "React-RCTText (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Text`)" - - "React-RCTVibration (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Vibration`)" - - "React-rendererconsistency (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/consistency`)" - - "React-renderercss (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/css`)" - - "React-rendererdebug (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/debug`)" - - "React-RuntimeApple (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/runtime/platform/ios`)" - - "React-RuntimeCore (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/runtime`)" - - "React-runtimeexecutor (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/runtimeexecutor`)" - - "React-RuntimeHermes (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/runtime`)" - - "React-runtimescheduler (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)" - - "React-timing (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/timing`)" - - "React-utils (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/utils`)" + - "React-NativeModulesApple (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`)" + - "React-oscompat (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/oscompat`)" + - "React-perflogger (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/reactperflogger`)" + - "React-performancetimeline (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/performance/timeline`)" + - "React-RCTActionSheet (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/ActionSheetIOS`)" + - "React-RCTAnimation (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/NativeAnimation`)" + - "React-RCTAppDelegate (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/AppDelegate`)" + - "React-RCTBlob (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Blob`)" + - "React-RCTFabric (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React`)" + - "React-RCTFBReactNativeSpec (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React`)" + - "React-RCTImage (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Image`)" + - "React-RCTLinking (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/LinkingIOS`)" + - "React-RCTNetwork (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Network`)" + - "React-RCTRuntime (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React/Runtime`)" + - "React-RCTSettings (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Settings`)" + - "React-RCTText (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Text`)" + - "React-RCTVibration (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Vibration`)" + - "React-rendererconsistency (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/consistency`)" + - "React-renderercss (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/css`)" + - "React-rendererdebug (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/debug`)" + - "React-RuntimeApple (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/runtime/platform/ios`)" + - "React-RuntimeCore (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/runtime`)" + - "React-runtimeexecutor (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/runtimeexecutor`)" + - "React-RuntimeHermes (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/runtime`)" + - "React-runtimescheduler (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`)" + - "React-timing (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/timing`)" + - "React-utils (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/utils`)" - ReactAppDependencyProvider (from `build/generated/ios`) - ReactCodegen (from `build/generated/ios`) - - "ReactCommon/turbomodule/core (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon`)" - - "ReactNativeDependencies (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)" - - "RNCAsyncStorage (from `../../../node_modules/.bun/@react-native-async-storage+async-storage@2.2.0+c1b4cdd6e4cb2f11/node_modules/@react-native-async-storage/async-storage`)" - - "RNCClipboard (from `../../../node_modules/.bun/@react-native-clipboard+clipboard@1.16.3+c1b4cdd6e4cb2f11/node_modules/@react-native-clipboard/clipboard`)" - - "RNGestureHandler (from `../../../node_modules/.bun/react-native-gesture-handler@2.28.0+c1b4cdd6e4cb2f11/node_modules/react-native-gesture-handler`)" - - "RNReanimated (from `../../../node_modules/.bun/react-native-reanimated@4.1.6+58856117def9c0ae/node_modules/react-native-reanimated`)" - - "RNScreens (from `../../../node_modules/.bun/react-native-screens@4.16.0+c1b4cdd6e4cb2f11/node_modules/react-native-screens`)" - - "RNSVG (from `../../../node_modules/.bun/react-native-svg@15.12.1+c1b4cdd6e4cb2f11/node_modules/react-native-svg`)" - - "RNWorklets (from `../../../node_modules/.bun/react-native-worklets@0.7.1+c1b4cdd6e4cb2f11/node_modules/react-native-worklets`)" + - "ReactCommon/turbomodule/core (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon`)" + - "ReactNativeDependencies (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec`)" + - "RNCAsyncStorage (from `../../../node_modules/.bun/@react-native-async-storage+async-storage@2.2.0+87dd5a4c738f4c73/node_modules/@react-native-async-storage/async-storage`)" + - "RNCClipboard (from `../../../node_modules/.bun/@react-native-clipboard+clipboard@1.16.3+87dd5a4c738f4c73/node_modules/@react-native-clipboard/clipboard`)" + - "RNGestureHandler (from `../../../node_modules/.bun/react-native-gesture-handler@2.28.0+87dd5a4c738f4c73/node_modules/react-native-gesture-handler`)" + - "RNReanimated (from `../../../node_modules/.bun/react-native-reanimated@4.1.6+d983531a34c8e10a/node_modules/react-native-reanimated`)" + - "RNScreens (from `../../../node_modules/.bun/react-native-screens@4.16.0+87dd5a4c738f4c73/node_modules/react-native-screens`)" + - "RNSVG (from `../../../node_modules/.bun/react-native-svg@15.12.1+87dd5a4c738f4c73/node_modules/react-native-svg`)" + - "RNWorklets (from `../../../node_modules/.bun/react-native-worklets@0.7.1+87dd5a4c738f4c73/node_modules/react-native-worklets`)" - SDMobileCore (from `../modules/sd-mobile-core/ios`) - - "Yoga (from `../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/yoga`)" + - "Yoga (from `../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/yoga`)" SPEC REPOS: trunk: @@ -2482,206 +2482,206 @@ SPEC REPOS: EXTERNAL SOURCES: EXConstants: - :path: "../../../node_modules/.bun/expo-constants@18.0.11+9bddab83625b4ca7/node_modules/expo-constants/ios" + :path: "../../../node_modules/.bun/expo-constants@18.0.11+3ee4ad13fefe912b/node_modules/expo-constants/ios" EXJSONUtils: :path: "../../../node_modules/.bun/expo-json-utils@0.15.0/node_modules/expo-json-utils/ios" EXManifests: - :path: "../../../node_modules/.bun/expo-manifests@1.0.10+9bddab83625b4ca7/node_modules/expo-manifests/ios" + :path: "../../../node_modules/.bun/expo-manifests@1.0.10+3ee4ad13fefe912b/node_modules/expo-manifests/ios" Expo: - :path: "../../../node_modules/.bun/expo@54.0.27+9bddab83625b4ca7/node_modules/expo" + :path: "../../../node_modules/.bun/expo@54.0.27+3ee4ad13fefe912b/node_modules/expo" expo-dev-client: - :path: "../../../node_modules/.bun/expo-dev-client@6.0.20+9bddab83625b4ca7/node_modules/expo-dev-client/ios" + :path: "../../../node_modules/.bun/expo-dev-client@6.0.20+3ee4ad13fefe912b/node_modules/expo-dev-client/ios" expo-dev-launcher: - :path: "../../../node_modules/.bun/expo-dev-launcher@6.0.20+9bddab83625b4ca7/node_modules/expo-dev-launcher" + :path: "../../../node_modules/.bun/expo-dev-launcher@6.0.20+3ee4ad13fefe912b/node_modules/expo-dev-launcher" expo-dev-menu: - :path: "../../../node_modules/.bun/expo-dev-menu@7.0.18+9bddab83625b4ca7/node_modules/expo-dev-menu" + :path: "../../../node_modules/.bun/expo-dev-menu@7.0.18+3ee4ad13fefe912b/node_modules/expo-dev-menu" expo-dev-menu-interface: - :path: "../../../node_modules/.bun/expo-dev-menu-interface@2.0.0+9bddab83625b4ca7/node_modules/expo-dev-menu-interface/ios" + :path: "../../../node_modules/.bun/expo-dev-menu-interface@2.0.0+3ee4ad13fefe912b/node_modules/expo-dev-menu-interface/ios" ExpoAsset: - :path: "../../../node_modules/.bun/expo-asset@12.0.11+9bddab83625b4ca7/node_modules/expo-asset/ios" + :path: "../../../node_modules/.bun/expo-asset@12.0.11+3ee4ad13fefe912b/node_modules/expo-asset/ios" ExpoBlur: - :path: "../../../node_modules/.bun/expo-blur@15.0.8+9bddab83625b4ca7/node_modules/expo-blur/ios" + :path: "../../../node_modules/.bun/expo-blur@15.0.8+3ee4ad13fefe912b/node_modules/expo-blur/ios" ExpoCamera: - :path: "../../../node_modules/.bun/expo-camera@17.0.10+9bddab83625b4ca7/node_modules/expo-camera/ios" + :path: "../../../node_modules/.bun/expo-camera@17.0.10+3ee4ad13fefe912b/node_modules/expo-camera/ios" ExpoDocumentPicker: - :path: "../../../node_modules/.bun/expo-document-picker@14.0.8+9bddab83625b4ca7/node_modules/expo-document-picker/ios" + :path: "../../../node_modules/.bun/expo-document-picker@14.0.8+3ee4ad13fefe912b/node_modules/expo-document-picker/ios" ExpoFileSystem: - :path: "../../../node_modules/.bun/expo-file-system@19.0.20+9bddab83625b4ca7/node_modules/expo-file-system/ios" + :path: "../../../node_modules/.bun/expo-file-system@19.0.20+3ee4ad13fefe912b/node_modules/expo-file-system/ios" ExpoFont: - :path: "../../../node_modules/.bun/expo-font@14.0.10+9915544d233ac918/node_modules/expo-font/ios" + :path: "../../../node_modules/.bun/expo-font@14.0.10+c262bee79918334c/node_modules/expo-font/ios" ExpoHaptics: - :path: "../../../node_modules/.bun/expo-haptics@15.0.8+9bddab83625b4ca7/node_modules/expo-haptics/ios" + :path: "../../../node_modules/.bun/expo-haptics@15.0.8+3ee4ad13fefe912b/node_modules/expo-haptics/ios" ExpoHead: - :path: "../../../node_modules/.bun/expo-router@6.0.17+74a3cf8ce2a4bc65/node_modules/expo-router/ios" + :path: "../../../node_modules/.bun/expo-router@6.0.17+7dc14032edcce378/node_modules/expo-router/ios" ExpoImage: - :path: "../../../node_modules/.bun/expo-image@3.0.11+9bddab83625b4ca7/node_modules/expo-image/ios" + :path: "../../../node_modules/.bun/expo-image@3.0.11+3ee4ad13fefe912b/node_modules/expo-image/ios" ExpoKeepAwake: :path: "../../../node_modules/.bun/expo-keep-awake@15.0.8+ddb0696906414ead/node_modules/expo-keep-awake/ios" ExpoLinking: - :path: "../../../node_modules/.bun/expo-linking@8.0.10+9bddab83625b4ca7/node_modules/expo-linking/ios" + :path: "../../../node_modules/.bun/expo-linking@8.0.10+3ee4ad13fefe912b/node_modules/expo-linking/ios" ExpoModulesCore: - :path: "../../../node_modules/.bun/expo-modules-core@3.0.28+c1b4cdd6e4cb2f11/node_modules/expo-modules-core" + :path: "../../../node_modules/.bun/expo-modules-core@3.0.28+87dd5a4c738f4c73/node_modules/expo-modules-core" ExpoSplashScreen: - :path: "../../../node_modules/.bun/expo-splash-screen@31.0.12+9bddab83625b4ca7/node_modules/expo-splash-screen/ios" + :path: "../../../node_modules/.bun/expo-splash-screen@31.0.12+3ee4ad13fefe912b/node_modules/expo-splash-screen/ios" EXUpdatesInterface: - :path: "../../../node_modules/.bun/expo-updates-interface@2.0.0+9bddab83625b4ca7/node_modules/expo-updates-interface/ios" + :path: "../../../node_modules/.bun/expo-updates-interface@2.0.0+3ee4ad13fefe912b/node_modules/expo-updates-interface/ios" FBLazyVector: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/FBLazyVector" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/FBLazyVector" hermes-engine: - :podspec: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :podspec: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" :tag: hermes-2025-07-07-RNv0.81.0-e0fc67142ec0763c6b6153ca2bf96df815539782 LiquidGlass: - :path: "../../../node_modules/.bun/@callstack+liquid-glass@0.7.0+c1b4cdd6e4cb2f11/node_modules/@callstack/liquid-glass" + :path: "../../../node_modules/.bun/@callstack+liquid-glass@0.7.0+87dd5a4c738f4c73/node_modules/@callstack/liquid-glass" RCTDeprecation: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" RCTRequired: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Required" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Required" RCTTypeSafety: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/TypeSafety" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/TypeSafety" React: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/" React-callinvoker: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/callinvoker" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/callinvoker" React-Core: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/" React-Core-prebuilt: - :podspec: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React-Core-prebuilt.podspec" + :podspec: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React-Core-prebuilt.podspec" React-CoreModules: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React/CoreModules" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React/CoreModules" React-cxxreact: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/cxxreact" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/cxxreact" React-debug: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/debug" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/debug" React-defaultsnativemodule: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/defaults" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/defaults" React-domnativemodule: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/dom" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/dom" React-Fabric: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon" React-FabricComponents: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon" React-FabricImage: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon" React-featureflags: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/featureflags" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/featureflags" React-featureflagsnativemodule: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/featureflags" React-graphics: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/graphics" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/graphics" React-hermes: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/hermes" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/hermes" React-idlecallbacksnativemodule: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" React-ImageManager: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" React-jserrorhandler: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jserrorhandler" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jserrorhandler" React-jsi: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsi" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsi" React-jsiexecutor: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsiexecutor" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsiexecutor" React-jsinspector: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsinspector-modern" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsinspector-modern" React-jsinspectorcdp: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsinspector-modern/cdp" React-jsinspectornetwork: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsinspector-modern/network" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsinspector-modern/network" React-jsinspectortracing: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsinspector-modern/tracing" React-jsitooling: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/jsitooling" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/jsitooling" React-jsitracing: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/hermes/executor/" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/hermes/executor/" React-logger: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/logger" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/logger" React-Mapbuffer: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon" React-microtasksnativemodule: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/microtasks" react-native-safe-area-context: - :path: "../../../node_modules/.bun/react-native-safe-area-context@5.6.2+c1b4cdd6e4cb2f11/node_modules/react-native-safe-area-context" + :path: "../../../node_modules/.bun/react-native-safe-area-context@5.6.2+87dd5a4c738f4c73/node_modules/react-native-safe-area-context" react-native-slider: :path: "../../../node_modules/.bun/@react-native-community+slider@5.1.1/node_modules/@react-native-community/slider" React-NativeModulesApple: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" React-oscompat: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/oscompat" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/oscompat" React-perflogger: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/reactperflogger" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/reactperflogger" React-performancetimeline: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/performance/timeline" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/performance/timeline" React-RCTActionSheet: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/ActionSheetIOS" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/ActionSheetIOS" React-RCTAnimation: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/NativeAnimation" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/NativeAnimation" React-RCTAppDelegate: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/AppDelegate" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/AppDelegate" React-RCTBlob: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Blob" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Blob" React-RCTFabric: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React" React-RCTFBReactNativeSpec: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React" React-RCTImage: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Image" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Image" React-RCTLinking: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/LinkingIOS" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/LinkingIOS" React-RCTNetwork: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Network" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Network" React-RCTRuntime: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/React/Runtime" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/React/Runtime" React-RCTSettings: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Settings" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Settings" React-RCTText: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Text" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Text" React-RCTVibration: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/Libraries/Vibration" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/Libraries/Vibration" React-rendererconsistency: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/consistency" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/consistency" React-renderercss: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/css" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/css" React-rendererdebug: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/debug" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/debug" React-RuntimeApple: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/runtime/platform/ios" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/runtime/platform/ios" React-RuntimeCore: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/runtime" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/runtime" React-runtimeexecutor: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/runtimeexecutor" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/runtimeexecutor" React-RuntimeHermes: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/runtime" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/runtime" React-runtimescheduler: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" React-timing: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/timing" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/timing" React-utils: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/react/utils" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/react/utils" ReactAppDependencyProvider: :path: build/generated/ios ReactCodegen: :path: build/generated/ios ReactCommon: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon" ReactNativeDependencies: - :podspec: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" + :podspec: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/third-party-podspecs/ReactNativeDependencies.podspec" RNCAsyncStorage: - :path: "../../../node_modules/.bun/@react-native-async-storage+async-storage@2.2.0+c1b4cdd6e4cb2f11/node_modules/@react-native-async-storage/async-storage" + :path: "../../../node_modules/.bun/@react-native-async-storage+async-storage@2.2.0+87dd5a4c738f4c73/node_modules/@react-native-async-storage/async-storage" RNCClipboard: - :path: "../../../node_modules/.bun/@react-native-clipboard+clipboard@1.16.3+c1b4cdd6e4cb2f11/node_modules/@react-native-clipboard/clipboard" + :path: "../../../node_modules/.bun/@react-native-clipboard+clipboard@1.16.3+87dd5a4c738f4c73/node_modules/@react-native-clipboard/clipboard" RNGestureHandler: - :path: "../../../node_modules/.bun/react-native-gesture-handler@2.28.0+c1b4cdd6e4cb2f11/node_modules/react-native-gesture-handler" + :path: "../../../node_modules/.bun/react-native-gesture-handler@2.28.0+87dd5a4c738f4c73/node_modules/react-native-gesture-handler" RNReanimated: - :path: "../../../node_modules/.bun/react-native-reanimated@4.1.6+58856117def9c0ae/node_modules/react-native-reanimated" + :path: "../../../node_modules/.bun/react-native-reanimated@4.1.6+d983531a34c8e10a/node_modules/react-native-reanimated" RNScreens: - :path: "../../../node_modules/.bun/react-native-screens@4.16.0+c1b4cdd6e4cb2f11/node_modules/react-native-screens" + :path: "../../../node_modules/.bun/react-native-screens@4.16.0+87dd5a4c738f4c73/node_modules/react-native-screens" RNSVG: - :path: "../../../node_modules/.bun/react-native-svg@15.12.1+c1b4cdd6e4cb2f11/node_modules/react-native-svg" + :path: "../../../node_modules/.bun/react-native-svg@15.12.1+87dd5a4c738f4c73/node_modules/react-native-svg" RNWorklets: - :path: "../../../node_modules/.bun/react-native-worklets@0.7.1+c1b4cdd6e4cb2f11/node_modules/react-native-worklets" + :path: "../../../node_modules/.bun/react-native-worklets@0.7.1+87dd5a4c738f4c73/node_modules/react-native-worklets" SDMobileCore: :path: "../modules/sd-mobile-core/ios" Yoga: - :path: "../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native/ReactCommon/yoga" + :path: "../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native/ReactCommon/yoga" SPEC CHECKSUMS: EXConstants: c378c1b344ff1ecfbad27b90f0e48d1d0ead8cbb @@ -2708,7 +2708,7 @@ SPEC CHECKSUMS: EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734 FBLazyVector: e95a291ad2dadb88e42b06e0c5fb8262de53ec12 hermes-engine: 9f4dfe93326146a1c99eb535b1cb0b857a3cd172 - libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 + libavif: 5f8e715bea24debec477006f21ef9e95432e254d libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f libwebp: 02b23773aedb6ff1fd38cec7a77b81414c6842a8 LiquidGlass: 2bbbcea458d5a2b018c2dd024040c4023d73eec8 @@ -2775,18 +2775,18 @@ SPEC CHECKSUMS: React-timing: 6fa9883de2e41791e5dc4ec404e5e37f3f50e801 React-utils: 6e2035b53d087927768649a11a26c4e092448e34 ReactAppDependencyProvider: 1bcd3527ac0390a1c898c114f81ff954be35ed79 - ReactCodegen: f7a907c67cee1b036d34e217698f27696f14f403 + ReactCodegen: 9c2af94dc4ee5c1b98fb465418036615be3793b3 ReactCommon: 08810150b1206cc44aecf5f6ae19af32f29151a8 ReactNativeDependencies: 71ce9c28beb282aa720ea7b46980fff9669f428a RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4 RNCClipboard: 88d7eeb555d1183915f0885bdbc5c97eb6f7f3ba RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3 - RNReanimated: a242d405fcd1b7206cfa42dfc24a747a6d2cac23 + RNReanimated: 83246804817326398f1506dd916bf6fe47fa6242 RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845 RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34 - RNWorklets: 64422f411fb58e8e20725853a2d7858b599af7e2 - SDMobileCore: ced74b2c27bd0e0d87f2281a6ddb7a209176ae50 - SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838 + RNWorklets: bdca513296f69bf7fe8418208da31447c65b23ed + SDMobileCore: 1dd1a6e1e9d5e9702dcca9cc51a87bc678b0a6c4 + SDWebImage: e9fc87c1aab89a8ab1bbd74eba378c6f53be8abf SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380 diff --git a/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj b/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj index 108f51ba7..8f7fba262 100644 --- a/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj +++ b/apps/mobile/ios/Spacedrive.xcodeproj/project.pbxproj @@ -462,7 +462,7 @@ LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; SWIFT_ENABLE_EXPLICIT_MODULES = NO; @@ -517,7 +517,7 @@ ); LIBRARY_SEARCH_PATHS = "$(SDKROOT)/usr/lib/swift\"$(inherited)\""; MTL_ENABLE_DEBUG_INFO = NO; - REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.bun/react-native@0.81.5+c1b4cdd6e4cb2f11/node_modules/react-native"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../../node_modules/.bun/react-native@0.81.5+87dd5a4c738f4c73/node_modules/react-native"; SDKROOT = iphoneos; SWIFT_ENABLE_EXPLICIT_MODULES = NO; USE_HERMES = true; diff --git a/core/src/data/manager.rs b/core/src/data/manager.rs new file mode 100644 index 000000000..3f46d8500 --- /dev/null +++ b/core/src/data/manager.rs @@ -0,0 +1,219 @@ +//! SourceManager: library-scoped wrapper around sd-archive Engine. + +use std::path::PathBuf; + +use sd_archive::{Engine, EngineConfig}; +use tracing::info; + +/// Manages archive data sources for a single library. +pub struct SourceManager { + engine: Engine, +} + +impl SourceManager { + /// Create a new source manager rooted at the library's archive directory. + pub async fn new(library_path: PathBuf) -> Result { + let data_dir = library_path.join("archive"); + + let config = EngineConfig { + data_dir: data_dir.clone(), + }; + let engine = Engine::new(config) + .await + .map_err(|e| format!("Failed to initialize archive engine: {e}"))?; + + // Sync bundled adapters from the source tree into the installed adapters + // directory. Uses CARGO_MANIFEST_DIR at compile time to find the workspace + // root, matching the pattern from the spacedrive-data prototype. + let installed_dir = data_dir.join("adapters"); + Self::sync_bundled_adapters(&installed_dir); + + // Reload adapters after sync (picks up any newly copied adapters) + Engine::load_script_adapters(&installed_dir, engine.adapters()) + .map_err(|e| format!("Failed to reload adapters: {e}"))?; + + info!("Source manager initialized at {}", library_path.display()); + + Ok(Self { engine }) + } + + /// Sync bundled adapters from the compile-time workspace into the installed + /// adapters directory. New adapters are copied; existing ones are updated if + /// the adapter.toml has changed. + fn sync_bundled_adapters(installed_dir: &std::path::Path) { + let source_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .map(|p| p.join("adapters")); + + let source_dir = match source_dir { + Some(d) if d.is_dir() => d, + _ => return, + }; + + info!( + "Syncing bundled adapters from {} to {}", + source_dir.display(), + installed_dir.display() + ); + + let entries = match std::fs::read_dir(&source_dir) { + Ok(e) => e, + Err(_) => return, + }; + + for entry in entries.flatten() { + let src_path = entry.path(); + if !src_path.is_dir() || !src_path.join("adapter.toml").exists() { + continue; + } + + let adapter_name = match src_path.file_name() { + Some(n) => n.to_owned(), + None => continue, + }; + let dest_path = installed_dir.join(&adapter_name); + + if !dest_path.exists() { + // New adapter — copy entire directory + if let Err(e) = copy_dir_recursive(&src_path, &dest_path) { + tracing::warn!( + adapter = ?adapter_name, + error = %e, + "failed to install bundled adapter" + ); + } else { + info!(adapter = ?adapter_name, "installed bundled adapter"); + } + } else { + // Existing adapter — update if adapter.toml changed + let src_manifest = std::fs::read_to_string(src_path.join("adapter.toml")); + let dest_manifest = std::fs::read_to_string(dest_path.join("adapter.toml")); + + if let (Ok(src), Ok(dest)) = (src_manifest, dest_manifest) { + if src != dest { + if let Err(e) = copy_dir_recursive(&src_path, &dest_path) { + tracing::warn!( + adapter = ?adapter_name, + error = %e, + "failed to update bundled adapter" + ); + } else { + info!(adapter = ?adapter_name, "updated bundled adapter"); + } + } + } + } + } + } + + /// List all sources. + pub async fn list_sources(&self) -> Result, String> { + self.engine + .list_sources() + .await + .map_err(|e| format!("Failed to list sources: {e}")) + } + + /// Create a new source. + pub async fn create_source( + &self, + name: &str, + adapter_id: &str, + config: serde_json::Value, + ) -> Result { + self.engine + .create_source(name, adapter_id, config) + .await + .map_err(|e| format!("Failed to create source: {e}")) + } + + /// Delete a source. + pub async fn delete_source(&self, source_id: &str) -> Result<(), String> { + self.engine + .delete_source(source_id) + .await + .map_err(|e| format!("Failed to delete source: {e}")) + } + + /// Sync a source. + pub async fn sync_source( + &self, + source_id: &str, + ) -> Result { + self.engine + .sync(source_id) + .await + .map_err(|e| format!("Failed to sync source: {e}")) + } + + /// List items from a source. + pub async fn list_items( + &self, + source_id: &str, + limit: usize, + offset: usize, + ) -> Result, String> { + self.engine + .list_items(source_id, limit, offset) + .await + .map_err(|e| format!("Failed to list items: {e}")) + } + + /// List available adapters with update status. + pub fn list_adapters(&self) -> Vec { + let source_dir = self.engine.source_adapters_dir(); + self.engine + .list_adapters_with_updates(source_dir.as_deref()) + } + + /// Update an installed adapter from its source directory. + pub fn update_adapter( + &self, + adapter_id: &str, + ) -> Result { + let source_dir = self + .engine + .source_adapters_dir() + .ok_or_else(|| "Cannot find source adapters directory".to_string())? + .join(adapter_id); + + if !source_dir.join("adapter.toml").exists() { + return Err(format!("No source adapter found for '{adapter_id}'")); + } + + self.engine + .update_adapter(adapter_id, &source_dir) + .map_err(|e| format!("Failed to update adapter: {e}")) + } + + /// Get config fields for an adapter. + pub fn adapter_config_fields( + &self, + adapter_id: &str, + ) -> Result, String> { + self.engine + .adapter_config_fields(adapter_id) + .map_err(|e| format!("Failed to get adapter config: {e}")) + } + + /// Get the underlying engine. + pub fn engine(&self) -> &Engine { + &self.engine + } +} + +/// Recursively copy a directory. +fn copy_dir_recursive(src: &std::path::Path, dest: &std::path::Path) -> Result<(), String> { + std::fs::create_dir_all(dest).map_err(|e| e.to_string())?; + for entry in std::fs::read_dir(src).map_err(|e| e.to_string())? { + let entry = entry.map_err(|e| e.to_string())?; + let src_path = entry.path(); + let dest_path = dest.join(entry.file_name()); + if src_path.is_dir() { + copy_dir_recursive(&src_path, &dest_path)?; + } else { + std::fs::copy(&src_path, &dest_path).map_err(|e| e.to_string())?; + } + } + Ok(()) +} diff --git a/core/src/data/mod.rs b/core/src/data/mod.rs new file mode 100644 index 000000000..81673674d --- /dev/null +++ b/core/src/data/mod.rs @@ -0,0 +1,5 @@ +//! Data module: manages archive data sources within a library. +//! +//! Wraps `sd-archive` engine to provide library-scoped source management. + +pub mod manager; diff --git a/core/src/domain/space.rs b/core/src/domain/space.rs index 1c0baf287..5ed2e643d 100644 --- a/core/src/domain/space.rs +++ b/core/src/domain/space.rs @@ -231,6 +231,9 @@ pub enum GroupType { /// Tag collection Tags, + /// Archive data sources (email, notes, bookmarks, etc.) + Sources, + /// Cloud storage providers Cloud, @@ -436,6 +439,12 @@ pub enum ItemType { /// Any arbitrary path (dragged from explorer) Path { sd_path: SdPath }, + + /// All archive data sources screen + Sources, + + /// Specific archive data source + Source { source_id: String }, } /// Complete sidebar layout for a space #[derive(Debug, Clone, Serialize, Deserialize, Type)] diff --git a/core/src/library/manager.rs b/core/src/library/manager.rs index afa7219a9..34b5d6c15 100644 --- a/core/src/library/manager.rs +++ b/core/src/library/manager.rs @@ -1438,6 +1438,45 @@ impl LibraryManager { info!("Created default Tags group for library {}", library.id()); + // Create Sources group + let sources_group_id = + deterministic_library_default_uuid(library_id, "space_group", "Sources"); + let sources_type_json = serde_json::to_string(&GroupType::Sources) + .map_err(|e| LibraryError::Other(format!("Failed to serialize group_type: {}", e)))?; + + let sources_group_model = crate::infra::db::entities::space_group::ActiveModel { + id: NotSet, + uuid: Set(sources_group_id), + space_id: Set(space_result.id), + name: Set("Sources".to_string()), + group_type: Set(sources_type_json), + is_collapsed: Set(true), + order: Set(4), + created_at: Set(now.into()), + }; + + // Use atomic upsert to handle race conditions with sync + GroupEntity::insert(sources_group_model) + .on_conflict( + sea_orm::sea_query::OnConflict::column(GroupColumn::Uuid) + .update_columns([ + GroupColumn::SpaceId, + GroupColumn::Name, + GroupColumn::GroupType, + GroupColumn::IsCollapsed, + GroupColumn::Order, + ]) + .to_owned(), + ) + .exec(db) + .await + .map_err(LibraryError::DatabaseError)?; + + info!( + "Created default Sources group for library {}", + library.id() + ); + Ok(()) } diff --git a/core/src/ops/adapters/config/mod.rs b/core/src/ops/adapters/config/mod.rs new file mode 100644 index 000000000..97e6ac2ba --- /dev/null +++ b/core/src/ops/adapters/config/mod.rs @@ -0,0 +1,5 @@ +//! Get adapter config fields + +pub mod query; + +pub use query::*; diff --git a/core/src/ops/adapters/config/query.rs b/core/src/ops/adapters/config/query.rs new file mode 100644 index 000000000..fe02325da --- /dev/null +++ b/core/src/ops/adapters/config/query.rs @@ -0,0 +1,91 @@ +//! Get adapter config fields query + +use crate::{ + context::CoreContext, + infra::query::{LibraryQuery, QueryError, QueryResult}, +}; +use serde::{Deserialize, Serialize}; +use specta::Type; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct GetAdapterConfigInput { + pub adapter_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct AdapterConfigField { + pub key: String, + pub name: String, + pub description: String, + pub field_type: String, + pub required: bool, + pub secret: bool, + pub default: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GetAdapterConfigQuery { + pub input: GetAdapterConfigInput, +} + +impl LibraryQuery for GetAdapterConfigQuery { + type Input = GetAdapterConfigInput; + type Output = Vec; + + fn from_input(input: Self::Input) -> QueryResult { + if input.adapter_id.trim().is_empty() { + return Err(QueryError::Validation { + field: "adapter_id".to_string(), + message: "adapter_id cannot be empty".to_string(), + }); + } + Ok(Self { input }) + } + + async fn execute( + self, + context: Arc, + session: crate::infra::api::SessionContext, + ) -> QueryResult { + let library_id = session + .current_library_id + .ok_or_else(|| QueryError::Internal("No library in session".to_string()))?; + let library = context + .libraries() + .await + .get_library(library_id) + .await + .ok_or_else(|| QueryError::Internal("Library not found".to_string()))?; + + if library.source_manager().is_none() { + library + .init_source_manager() + .await + .map_err(|e| QueryError::Internal(format!("Failed to init source manager: {e}")))?; + } + + let source_manager = library + .source_manager() + .ok_or_else(|| QueryError::Internal("Source manager not available".to_string()))?; + + let fields = source_manager + .adapter_config_fields(&self.input.adapter_id) + .map_err(|e| QueryError::Internal(e))?; + + Ok(fields + .into_iter() + .map(|f| AdapterConfigField { + key: f.key, + name: f.name, + description: f.description, + field_type: f.field_type, + required: f.required, + secret: f.secret, + default: f.default.map(|d| d.to_string()), + }) + .collect()) + } +} + +crate::register_library_query!(GetAdapterConfigQuery, "adapters.config"); diff --git a/core/src/ops/adapters/list/mod.rs b/core/src/ops/adapters/list/mod.rs new file mode 100644 index 000000000..0415b37ef --- /dev/null +++ b/core/src/ops/adapters/list/mod.rs @@ -0,0 +1,5 @@ +//! List available adapters + +pub mod query; + +pub use query::*; diff --git a/core/src/ops/adapters/list/query.rs b/core/src/ops/adapters/list/query.rs new file mode 100644 index 000000000..43c3e6480 --- /dev/null +++ b/core/src/ops/adapters/list/query.rs @@ -0,0 +1,83 @@ +//! List available adapters query + +use crate::{ + context::CoreContext, + infra::query::{LibraryQuery, QueryError, QueryResult}, +}; +use serde::{Deserialize, Serialize}; +use specta::Type; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct ListAdaptersInput {} + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct AdapterInfo { + pub id: String, + pub name: String, + pub description: String, + pub version: String, + pub author: String, + pub data_type: String, + pub icon_svg: Option, + pub update_available: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ListAdaptersQuery { + pub input: ListAdaptersInput, +} + +impl LibraryQuery for ListAdaptersQuery { + type Input = ListAdaptersInput; + type Output = Vec; + + fn from_input(input: Self::Input) -> QueryResult { + Ok(Self { input }) + } + + async fn execute( + self, + context: Arc, + session: crate::infra::api::SessionContext, + ) -> QueryResult { + let library_id = session + .current_library_id + .ok_or_else(|| QueryError::Internal("No library in session".to_string()))?; + let library = context + .libraries() + .await + .get_library(library_id) + .await + .ok_or_else(|| QueryError::Internal("Library not found".to_string()))?; + + if library.source_manager().is_none() { + library + .init_source_manager() + .await + .map_err(|e| QueryError::Internal(format!("Failed to init source manager: {e}")))?; + } + + let source_manager = library + .source_manager() + .ok_or_else(|| QueryError::Internal("Source manager not available".to_string()))?; + + let adapters = source_manager.list_adapters(); + + Ok(adapters + .into_iter() + .map(|a| AdapterInfo { + id: a.id, + name: a.name, + description: a.description, + version: a.version, + author: a.author, + data_type: a.data_type, + icon_svg: a.icon_svg, + update_available: a.update_available, + }) + .collect()) + } +} + +crate::register_library_query!(ListAdaptersQuery, "adapters.list"); diff --git a/core/src/ops/adapters/mod.rs b/core/src/ops/adapters/mod.rs new file mode 100644 index 000000000..df55867e1 --- /dev/null +++ b/core/src/ops/adapters/mod.rs @@ -0,0 +1,9 @@ +//! Adapter operations for archive data sources. + +pub mod config; +pub mod list; +pub mod update; + +pub use config::*; +pub use list::*; +pub use update::*; diff --git a/core/src/ops/adapters/update/action.rs b/core/src/ops/adapters/update/action.rs new file mode 100644 index 000000000..981517def --- /dev/null +++ b/core/src/ops/adapters/update/action.rs @@ -0,0 +1,73 @@ +//! Adapter update action handler + +use crate::{ + context::CoreContext, + infra::action::{error::ActionError, LibraryAction}, + library::Library, +}; +use serde::{Deserialize, Serialize}; +use specta::Type; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct UpdateAdapterInput { + pub adapter_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct UpdateAdapterOutput { + pub adapter_id: String, + pub old_version: String, + pub new_version: String, + pub schema_changed: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct UpdateAdapterAction { + input: UpdateAdapterInput, +} + +impl LibraryAction for UpdateAdapterAction { + type Input = UpdateAdapterInput; + type Output = UpdateAdapterOutput; + + fn from_input(input: UpdateAdapterInput) -> Result { + if input.adapter_id.trim().is_empty() { + return Err("Adapter ID cannot be empty".to_string()); + } + Ok(Self { input }) + } + + async fn execute( + self, + library: Arc, + _context: Arc, + ) -> Result { + if library.source_manager().is_none() { + library.init_source_manager().await.map_err(|e| { + ActionError::Internal(format!("Failed to init source manager: {e}")) + })?; + } + + let source_manager = library + .source_manager() + .ok_or_else(|| ActionError::Internal("Source manager not available".to_string()))?; + + let result = source_manager + .update_adapter(&self.input.adapter_id) + .map_err(|e| ActionError::Internal(e))?; + + Ok(UpdateAdapterOutput { + adapter_id: result.adapter_id, + old_version: result.old_version, + new_version: result.new_version, + schema_changed: result.schema_changed, + }) + } + + fn action_kind(&self) -> &'static str { + "adapters.update" + } +} + +crate::register_library_action!(UpdateAdapterAction, "adapters.update"); diff --git a/core/src/ops/adapters/update/mod.rs b/core/src/ops/adapters/update/mod.rs new file mode 100644 index 000000000..dc6bc2684 --- /dev/null +++ b/core/src/ops/adapters/update/mod.rs @@ -0,0 +1,5 @@ +//! Update an adapter + +pub mod action; + +pub use action::*; diff --git a/core/src/ops/mod.rs b/core/src/ops/mod.rs index 000afd339..e0ba5ff9a 100644 --- a/core/src/ops/mod.rs +++ b/core/src/ops/mod.rs @@ -25,6 +25,7 @@ pub mod models; pub mod network; pub mod search; pub mod sidecar; +pub mod adapters; pub mod sources; pub mod spaces; pub mod sync; diff --git a/core/src/ops/sources/delete/action.rs b/core/src/ops/sources/delete/action.rs new file mode 100644 index 000000000..f8230a9b1 --- /dev/null +++ b/core/src/ops/sources/delete/action.rs @@ -0,0 +1,66 @@ +//! Source deletion action handler + +use crate::{ + context::CoreContext, + infra::action::{error::ActionError, LibraryAction}, + library::Library, +}; +use serde::{Deserialize, Serialize}; +use specta::Type; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct DeleteSourceInput { + pub source_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct DeleteSourceOutput { + pub deleted: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DeleteSourceAction { + input: DeleteSourceInput, +} + +impl LibraryAction for DeleteSourceAction { + type Input = DeleteSourceInput; + type Output = DeleteSourceOutput; + + fn from_input(input: DeleteSourceInput) -> Result { + if input.source_id.trim().is_empty() { + return Err("Source ID cannot be empty".to_string()); + } + Ok(Self { input }) + } + + async fn execute( + self, + library: Arc, + _context: Arc, + ) -> Result { + if library.source_manager().is_none() { + library.init_source_manager().await.map_err(|e| { + ActionError::Internal(format!("Failed to init source manager: {e}")) + })?; + } + + let source_manager = library + .source_manager() + .ok_or_else(|| ActionError::Internal("Source manager not available".to_string()))?; + + source_manager + .delete_source(&self.input.source_id) + .await + .map_err(|e| ActionError::Internal(e))?; + + Ok(DeleteSourceOutput { deleted: true }) + } + + fn action_kind(&self) -> &'static str { + "sources.delete" + } +} + +crate::register_library_action!(DeleteSourceAction, "sources.delete"); diff --git a/core/src/ops/sources/delete/mod.rs b/core/src/ops/sources/delete/mod.rs new file mode 100644 index 000000000..e71a5a0f5 --- /dev/null +++ b/core/src/ops/sources/delete/mod.rs @@ -0,0 +1,5 @@ +//! Source deletion action + +pub mod action; + +pub use action::*; diff --git a/core/src/ops/sources/get/mod.rs b/core/src/ops/sources/get/mod.rs new file mode 100644 index 000000000..2203790e8 --- /dev/null +++ b/core/src/ops/sources/get/mod.rs @@ -0,0 +1,5 @@ +//! Get a single source by ID + +pub mod query; + +pub use query::*; diff --git a/core/src/ops/sources/get/query.rs b/core/src/ops/sources/get/query.rs new file mode 100644 index 000000000..c1ed24364 --- /dev/null +++ b/core/src/ops/sources/get/query.rs @@ -0,0 +1,90 @@ +//! Single source query implementation + +use crate::ops::sources::list::SourceInfo; +use crate::{ + context::CoreContext, + infra::query::{LibraryQuery, QueryError, QueryResult}, +}; +use serde::{Deserialize, Serialize}; +use specta::Type; +use std::sync::Arc; +use uuid::Uuid; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct GetSourceInput { + pub source_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct GetSourceQuery { + pub input: GetSourceInput, +} + +impl LibraryQuery for GetSourceQuery { + type Input = GetSourceInput; + type Output = SourceInfo; + + fn from_input(input: Self::Input) -> QueryResult { + if input.source_id.trim().is_empty() { + return Err(QueryError::Validation { + field: "source_id".to_string(), + message: "source_id cannot be empty".to_string(), + }); + } + Ok(Self { input }) + } + + async fn execute( + self, + context: Arc, + session: crate::infra::api::SessionContext, + ) -> QueryResult { + let library_id = session + .current_library_id + .ok_or_else(|| QueryError::Internal("No library in session".to_string()))?; + let library = context + .libraries() + .await + .get_library(library_id) + .await + .ok_or_else(|| QueryError::Internal("Library not found".to_string()))?; + + if library.source_manager().is_none() { + library + .init_source_manager() + .await + .map_err(|e| QueryError::Internal(format!("Failed to init source manager: {e}")))?; + } + + let source_manager = library + .source_manager() + .ok_or_else(|| QueryError::Internal("Source manager not available".to_string()))?; + + let sources = source_manager + .list_sources() + .await + .map_err(|e| QueryError::Internal(e))?; + + let source = sources + .into_iter() + .find(|s| s.id == self.input.source_id) + .ok_or_else(|| { + QueryError::Internal(format!("Source not found: {}", self.input.source_id)) + })?; + + let id = Uuid::parse_str(&source.id) + .map_err(|e| QueryError::Internal(format!("Invalid source ID: {e}")))?; + + Ok(SourceInfo::new( + id, + source.name, + source.data_type, + source.adapter_id, + source.item_count, + source.last_synced, + source.status, + )) + } +} + +crate::register_library_query!(GetSourceQuery, "sources.get"); diff --git a/core/src/ops/sources/list_items/mod.rs b/core/src/ops/sources/list_items/mod.rs new file mode 100644 index 000000000..0ea80d079 --- /dev/null +++ b/core/src/ops/sources/list_items/mod.rs @@ -0,0 +1,5 @@ +//! Source items listing query + +pub mod query; + +pub use query::*; diff --git a/core/src/ops/sources/list_items/query.rs b/core/src/ops/sources/list_items/query.rs new file mode 100644 index 000000000..1ed5c8297 --- /dev/null +++ b/core/src/ops/sources/list_items/query.rs @@ -0,0 +1,94 @@ +//! Source items listing query implementation + +use crate::{ + context::CoreContext, + infra::query::{LibraryQuery, QueryError, QueryResult}, +}; +use serde::{Deserialize, Serialize}; +use specta::Type; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct ListSourceItemsInput { + pub source_id: String, + pub limit: u32, + pub offset: u32, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct SourceItem { + pub id: String, + pub external_id: String, + pub title: String, + pub preview: Option, + pub subtitle: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ListSourceItemsQuery { + pub input: ListSourceItemsInput, +} + +impl LibraryQuery for ListSourceItemsQuery { + type Input = ListSourceItemsInput; + type Output = Vec; + + fn from_input(input: Self::Input) -> QueryResult { + if input.source_id.trim().is_empty() { + return Err(QueryError::Validation { + field: "source_id".to_string(), + message: "source_id cannot be empty".to_string(), + }); + } + Ok(Self { input }) + } + + async fn execute( + self, + context: Arc, + session: crate::infra::api::SessionContext, + ) -> QueryResult { + let library_id = session + .current_library_id + .ok_or_else(|| QueryError::Internal("No library in session".to_string()))?; + let library = context + .libraries() + .await + .get_library(library_id) + .await + .ok_or_else(|| QueryError::Internal("Library not found".to_string()))?; + + if library.source_manager().is_none() { + library + .init_source_manager() + .await + .map_err(|e| QueryError::Internal(format!("Failed to init source manager: {e}")))?; + } + + let source_manager = library + .source_manager() + .ok_or_else(|| QueryError::Internal("Source manager not available".to_string()))?; + + let items = source_manager + .list_items( + &self.input.source_id, + self.input.limit as usize, + self.input.offset as usize, + ) + .await + .map_err(|e| QueryError::Internal(e))?; + + Ok(items + .into_iter() + .map(|item| SourceItem { + id: item.id, + external_id: item.external_id, + title: item.title, + preview: item.preview, + subtitle: item.subtitle, + }) + .collect()) + } +} + +crate::register_library_query!(ListSourceItemsQuery, "sources.list_items"); diff --git a/core/src/ops/sources/mod.rs b/core/src/ops/sources/mod.rs index d6074832a..015bc5282 100644 --- a/core/src/ops/sources/mod.rs +++ b/core/src/ops/sources/mod.rs @@ -4,7 +4,15 @@ //! like emails, notes, bookmarks, etc. from various adapters. pub mod create; +pub mod delete; +pub mod get; pub mod list; +pub mod list_items; +pub mod sync; pub use create::*; +pub use delete::*; +pub use get::*; pub use list::*; +pub use list_items::*; +pub use sync::*; diff --git a/core/src/ops/sources/sync/action.rs b/core/src/ops/sources/sync/action.rs new file mode 100644 index 000000000..290698ba1 --- /dev/null +++ b/core/src/ops/sources/sync/action.rs @@ -0,0 +1,74 @@ +//! Source sync action handler + +use crate::{ + context::CoreContext, + infra::action::{error::ActionError, LibraryAction}, + library::Library, +}; +use serde::{Deserialize, Serialize}; +use specta::Type; +use std::sync::Arc; + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct SyncSourceInput { + pub source_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, Type)] +pub struct SyncSourceOutput { + pub records_upserted: u64, + pub records_deleted: u64, + pub duration_ms: u64, + pub error: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct SyncSourceAction { + input: SyncSourceInput, +} + +impl LibraryAction for SyncSourceAction { + type Input = SyncSourceInput; + type Output = SyncSourceOutput; + + fn from_input(input: SyncSourceInput) -> Result { + if input.source_id.trim().is_empty() { + return Err("Source ID cannot be empty".to_string()); + } + Ok(Self { input }) + } + + async fn execute( + self, + library: Arc, + _context: Arc, + ) -> Result { + if library.source_manager().is_none() { + library.init_source_manager().await.map_err(|e| { + ActionError::Internal(format!("Failed to init source manager: {e}")) + })?; + } + + let source_manager = library + .source_manager() + .ok_or_else(|| ActionError::Internal("Source manager not available".to_string()))?; + + let report = source_manager + .sync_source(&self.input.source_id) + .await + .map_err(|e| ActionError::Internal(e))?; + + Ok(SyncSourceOutput { + records_upserted: report.records_upserted, + records_deleted: report.records_deleted, + duration_ms: report.duration_ms, + error: report.error, + }) + } + + fn action_kind(&self) -> &'static str { + "sources.sync" + } +} + +crate::register_library_action!(SyncSourceAction, "sources.sync"); diff --git a/core/src/ops/sources/sync/mod.rs b/core/src/ops/sources/sync/mod.rs new file mode 100644 index 000000000..e913552b8 --- /dev/null +++ b/core/src/ops/sources/sync/mod.rs @@ -0,0 +1,5 @@ +//! Source sync action + +pub mod action; + +pub use action::*; diff --git a/crates/archive/src/engine.rs b/crates/archive/src/engine.rs index 4c3497dd7..986f68dd1 100644 --- a/crates/archive/src/engine.rs +++ b/crates/archive/src/engine.rs @@ -92,7 +92,7 @@ impl Engine { } /// Load all script adapters from the adapters directory. - fn load_script_adapters( + pub fn load_script_adapters( adapters_dir: &std::path::Path, registry: &AdapterRegistry, ) -> Result<()> { @@ -529,6 +529,142 @@ impl Engine { Ok(manifest.adapter.config) } + /// Check whether a source adapter directory has changed compared to the installed version. + pub fn check_adapter_update( + &self, + adapter_id: &str, + source_dir: &std::path::Path, + ) -> Option { + let installed_toml = self + .config + .data_dir + .join("adapters") + .join(adapter_id) + .join("adapter.toml"); + let source_toml = source_dir.join("adapter.toml"); + + if !installed_toml.exists() || !source_toml.exists() { + return None; + } + + let installed_content = std::fs::read(&installed_toml).ok()?; + let source_content = std::fs::read(&source_toml).ok()?; + + let installed_hash = blake3::hash(&installed_content); + let source_hash = blake3::hash(&source_content); + + Some(installed_hash != source_hash) + } + + /// List adapters with update-available status. + pub fn list_adapters_with_updates( + &self, + source_adapters_dir: Option<&std::path::Path>, + ) -> Vec { + let mut infos = self.adapters.list(); + + if let Some(source_dir) = source_adapters_dir { + for info in &mut infos { + let adapter_source = source_dir.join(&info.id); + if let Some(has_update) = self.check_adapter_update(&info.id, &adapter_source) { + info.update_available = has_update; + } + } + } + + infos + } + + /// The path to the bundled adapters directory (workspace root's adapters/). + pub fn source_adapters_dir(&self) -> Option { + let candidates = [ + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .parent() + .map(|p| p.join("adapters")), + Some(self.config.data_dir.join("bundled_adapters")), + ]; + + for candidate in candidates.into_iter().flatten() { + if candidate.is_dir() { + return Some(candidate); + } + } + + None + } + + /// Update an installed adapter from a source directory. + /// + /// Backs up the installed adapter, copies new files, and re-registers. + /// Schema migrations happen automatically on next sync. + pub fn update_adapter( + &self, + adapter_id: &str, + source_dir: &std::path::Path, + ) -> Result { + let new_adapter = ScriptAdapter::from_dir(source_dir)?; + if new_adapter.id() != adapter_id { + return Err(Error::Other(format!( + "adapter ID mismatch: expected '{}', got '{}'", + adapter_id, + new_adapter.id() + ))); + } + + let installed_dir = self.config.data_dir.join("adapters").join(adapter_id); + if !installed_dir.exists() { + return Err(Error::AdapterNotFound(adapter_id.to_string())); + } + + // Read old version + let old_manifest = crate::adapter::script::AdapterManifest::from_file( + &installed_dir.join("adapter.toml"), + )?; + let old_version = old_manifest.adapter.version.clone(); + let new_version = new_adapter.manifest().adapter.version.clone(); + + // Schema diff + let old_schema = ScriptAdapter::from_dir(&installed_dir)?.schema().clone(); + let new_schema = new_adapter.schema().clone(); + let schema_changed = crate::schema::migration::schema_hash(&old_schema) + != crate::schema::migration::schema_hash(&new_schema); + + // Backup + let backup_name = format!( + "{}.bak.{}", + adapter_id, + chrono::Utc::now().format("%Y%m%d_%H%M%S") + ); + let backup_dir = self.config.data_dir.join("adapters").join(&backup_name); + std::fs::rename(&installed_dir, &backup_dir)?; + + tracing::info!(adapter_id, backup = %backup_dir.display(), "backed up adapter before update"); + + // Copy new files (restore backup on failure) + if let Err(e) = copy_dir_recursive(source_dir, &installed_dir) { + tracing::error!(adapter_id, error = %e, "update failed, restoring backup"); + if installed_dir.exists() { + let _ = std::fs::remove_dir_all(&installed_dir); + } + std::fs::rename(&backup_dir, &installed_dir)?; + return Err(e); + } + + // Re-register + let adapter = ScriptAdapter::from_dir(&installed_dir)?; + self.adapters.register(Arc::new(adapter)); + + tracing::info!(adapter_id, %old_version, %new_version, schema_changed, "adapter updated"); + + Ok(crate::adapter::AdapterUpdateResult { + adapter_id: adapter_id.to_string(), + old_version, + new_version, + schema_changed, + backup_path: backup_dir.to_string_lossy().to_string(), + }) + } + /// Install a script adapter from a directory path (sideloading). pub fn install_adapter(&self, source_dir: &std::path::Path) -> Result { let adapter = ScriptAdapter::from_dir(source_dir)?; diff --git a/packages/interface/src/Spacebot/SpacebotContext.tsx b/packages/interface/src/Spacebot/SpacebotContext.tsx index 94c2fa3e2..fac3183f1 100644 --- a/packages/interface/src/Spacebot/SpacebotContext.tsx +++ b/packages/interface/src/Spacebot/SpacebotContext.tsx @@ -1,12 +1,10 @@ import { - Atom, Brain, CalendarDots, - CaretDown, ChatCircleDots, Checks, - ClockCounterClockwise, - DotsThree + DotsThree, + MoonStars } from '@phosphor-icons/react'; import {Ball, BallBlue} from '@sd/assets/images'; import { @@ -39,7 +37,7 @@ export const primaryItems = [ {icon: ChatCircleDots, label: 'Chat', path: '/spacebot/chat'}, {icon: Checks, label: 'Tasks', path: '/spacebot/tasks'}, {icon: Brain, label: 'Memories', path: '/spacebot/memories'}, - {icon: Atom, label: 'Autonomy', path: '/spacebot/autonomy'}, + {icon: MoonStars, label: 'Dream', path: '/spacebot/autonomy'}, {icon: CalendarDots, label: 'Schedule', path: '/spacebot/schedule'} ]; diff --git a/packages/interface/src/Spacebot/SpacebotLayout.tsx b/packages/interface/src/Spacebot/SpacebotLayout.tsx index d987e018a..6defd61e6 100644 --- a/packages/interface/src/Spacebot/SpacebotLayout.tsx +++ b/packages/interface/src/Spacebot/SpacebotLayout.tsx @@ -42,11 +42,11 @@ function SidebarHistoryItem({ isActive ? 'bg-sidebar-selected/40' : 'hover:bg-sidebar-box' }`} > -
-
+
+
{conversation.title}
-
+
{conversation.last_message_preview ?? 'No messages yet'}
diff --git a/packages/interface/src/Spacebot/routes/TasksRoute.tsx b/packages/interface/src/Spacebot/routes/TasksRoute.tsx index b7a6ac3c1..dca9c142d 100644 --- a/packages/interface/src/Spacebot/routes/TasksRoute.tsx +++ b/packages/interface/src/Spacebot/routes/TasksRoute.tsx @@ -30,7 +30,7 @@ export function TasksRoute() { const tasksQuery = useQuery({ queryKey, - queryFn: () => apiClient.listTasks({agent_id: selectedAgent, limit: 200}), + queryFn: () => apiClient.listTasks(selectedAgent, 200), refetchInterval: 5000, }); diff --git a/packages/interface/src/components/Sources/SourceCard.tsx b/packages/interface/src/components/Sources/SourceCard.tsx new file mode 100644 index 000000000..59e69c237 --- /dev/null +++ b/packages/interface/src/components/Sources/SourceCard.tsx @@ -0,0 +1,66 @@ +import { useNavigate } from "react-router-dom"; +import { useAdapterIcons } from "../../hooks/useAdapterIcons"; +import { SourceTypeIcon } from "./SourceTypeIcon"; +import { SourceStatusBadge } from "./SourceStatusBadge"; + +interface SourceCardProps { + source: { + id: string; + name: string; + data_type: string; + adapter_id: string; + status: string; + item_count: number; + last_synced: string | null; + }; +} + +function formatRelative(iso: string): string { + const date = new Date(iso); + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const diffMin = Math.floor(diffMs / 60000); + const diffHr = Math.floor(diffMin / 60); + const diffDay = Math.floor(diffHr / 24); + + if (diffMin < 1) return "just now"; + if (diffMin < 60) return `${diffMin}m ago`; + if (diffHr < 24) return `${diffHr}h ago`; + if (diffDay < 30) return `${diffDay}d ago`; + return date.toLocaleDateString(); +} + +export function SourceCard({ source }: SourceCardProps) { + const navigate = useNavigate(); + const { getIcon } = useAdapterIcons(); + + return ( + + ); +} diff --git a/packages/interface/src/components/Sources/SourceDataRow.tsx b/packages/interface/src/components/Sources/SourceDataRow.tsx new file mode 100644 index 000000000..1d6ec541d --- /dev/null +++ b/packages/interface/src/components/Sources/SourceDataRow.tsx @@ -0,0 +1,55 @@ +interface SourceDataRowProps { + title: string; + preview?: string | null; + subtitle?: string | null; + date?: string | null; +} + +function formatDate(iso: string): string { + try { + const d = new Date(iso); + if (isNaN(d.getTime())) return iso; + return d.toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "numeric", + }); + } catch { + return iso; + } +} + +function cleanPreview(text: string): string { + return text.replace(/[\r\n\t]+/g, " ").replace(/\s{2,}/g, " ").trim(); +} + +export function SourceDataRow({ + title, + preview, + subtitle, + date, +}: SourceDataRowProps) { + return ( +
+ {/* Title row */} +
+ + {title} + + {date && ( + + {formatDate(date)} + + )} +
+ {subtitle && ( +

{subtitle}

+ )} + {preview && ( +

+ {cleanPreview(preview)} +

+ )} +
+ ); +} diff --git a/packages/interface/src/components/Sources/SourcePathBar.tsx b/packages/interface/src/components/Sources/SourcePathBar.tsx new file mode 100644 index 000000000..49cdc809e --- /dev/null +++ b/packages/interface/src/components/Sources/SourcePathBar.tsx @@ -0,0 +1,39 @@ +import { CaretRight, Database } from "@phosphor-icons/react"; +import { useNavigate } from "react-router-dom"; + +interface SourcePathBarProps { + sourceName: string; + itemCount: number; +} + +export function SourcePathBar({ + sourceName, + itemCount, +}: SourcePathBarProps) { + const navigate = useNavigate(); + + return ( +
+ + + + + + + + {sourceName} + + + + {itemCount.toLocaleString()} items + +
+ ); +} diff --git a/packages/interface/src/components/Sources/SourceStatusBadge.tsx b/packages/interface/src/components/Sources/SourceStatusBadge.tsx new file mode 100644 index 000000000..9f4f47e14 --- /dev/null +++ b/packages/interface/src/components/Sources/SourceStatusBadge.tsx @@ -0,0 +1,20 @@ +interface SourceStatusBadgeProps { + status: string; +} + +export function SourceStatusBadge({ status }: SourceStatusBadgeProps) { + return ( + + + {status} + + ); +} diff --git a/packages/interface/src/components/Sources/SourceTypeIcon.tsx b/packages/interface/src/components/Sources/SourceTypeIcon.tsx new file mode 100644 index 000000000..98e822ac5 --- /dev/null +++ b/packages/interface/src/components/Sources/SourceTypeIcon.tsx @@ -0,0 +1,46 @@ +interface SourceTypeIconProps { + type: string; + svg?: string | null; + size?: "sm" | "md" | "lg"; +} + +const SIZE_CLASSES = { + sm: "h-5 w-5 p-0.5", + md: "h-8 w-8 p-1.5", + lg: "h-10 w-10 p-2", +}; + +const FALLBACK_LABELS: Record = { + email: "mail", + file: "doc", + note: "note", + bookmark: "link", + history: "time", + markdown: "md", + session: "term", +}; + +export function SourceTypeIcon({ + type: typeName, + svg, + size = "md", +}: SourceTypeIconProps) { + if (svg) { + return ( +
svg]:h-full [&>svg]:w-full ${SIZE_CLASSES[size]}`} + dangerouslySetInnerHTML={{ __html: svg }} + /> + ); + } + + const label = FALLBACK_LABELS[typeName] ?? typeName.slice(0, 4); + + return ( +
+ {label} +
+ ); +} diff --git a/packages/interface/src/components/SpacesSidebar/SourcesGroup.tsx b/packages/interface/src/components/SpacesSidebar/SourcesGroup.tsx new file mode 100644 index 000000000..9029226ba --- /dev/null +++ b/packages/interface/src/components/SpacesSidebar/SourcesGroup.tsx @@ -0,0 +1,80 @@ +import { useNavigate, useLocation } from "react-router-dom"; +import { Database } from "@phosphor-icons/react"; +import { useLibraryQuery } from "../../contexts/SpacedriveContext"; +import { useAdapterIcons } from "../../hooks/useAdapterIcons"; +import { GroupHeader } from "./GroupHeader"; + +interface SourcesGroupProps { + isCollapsed: boolean; + onToggle: () => void; + sortableAttributes?: any; + sortableListeners?: any; +} + +export function SourcesGroup({ + isCollapsed, + onToggle, + sortableAttributes, + sortableListeners, +}: SourcesGroupProps) { + const navigate = useNavigate(); + const location = useLocation(); + const { getIcon } = useAdapterIcons(); + + const { data: sources } = useLibraryQuery({ + type: "sources.list", + input: { data_type: null }, + }); + + const sourcesList = sources ?? []; + + return ( +
+ + + {!isCollapsed && ( +
+ {sourcesList.map((source) => { + const isActive = location.pathname === `/sources/${source.id}`; + return ( + + ); + })} +
+ )} +
+ ); +} diff --git a/packages/interface/src/components/SpacesSidebar/SpaceCustomizationPanel.tsx b/packages/interface/src/components/SpacesSidebar/SpaceCustomizationPanel.tsx index 85143fb3e..058fe1aca 100644 --- a/packages/interface/src/components/SpacesSidebar/SpaceCustomizationPanel.tsx +++ b/packages/interface/src/components/SpacesSidebar/SpaceCustomizationPanel.tsx @@ -31,6 +31,10 @@ const PALETTE_ITEMS: PaletteItem[] = [ type: "FileKinds", label: "File Kinds", }, + { + type: "Sources", + label: "Sources", + }, ]; function DraggablePaletteItem({ item }: { item: PaletteItem }) { diff --git a/packages/interface/src/components/SpacesSidebar/SpaceGroup.tsx b/packages/interface/src/components/SpacesSidebar/SpaceGroup.tsx index 2d1ad007d..b5f741aef 100644 --- a/packages/interface/src/components/SpacesSidebar/SpaceGroup.tsx +++ b/packages/interface/src/components/SpacesSidebar/SpaceGroup.tsx @@ -8,6 +8,7 @@ import { DevicesGroup } from "./DevicesGroup"; import { LocationsGroup } from "./LocationsGroup"; import { VolumesGroup } from "./VolumesGroup"; import { TagsGroup } from "./TagsGroup"; +import { SourcesGroup } from "./SourcesGroup"; import { GroupHeader } from "./GroupHeader"; import { useDroppable, useDndContext } from "@dnd-kit/core"; @@ -115,6 +116,20 @@ export function SpaceGroup({ ); } + // Sources group - fetches archive data sources + if (group.group_type === "Sources") { + return ( +
+ +
+ ); + } + // Empty drop zone for groups with no items const { setNodeRef: setEmptyRef, isOver: isOverEmpty } = useDroppable({ id: `group-${group.id}-empty`, diff --git a/packages/interface/src/components/SpacesSidebar/hooks/spaceItemUtils.ts b/packages/interface/src/components/SpacesSidebar/hooks/spaceItemUtils.ts index f6c2e8d11..a7d4f1311 100644 --- a/packages/interface/src/components/SpacesSidebar/hooks/spaceItemUtils.ts +++ b/packages/interface/src/components/SpacesSidebar/hooks/spaceItemUtils.ts @@ -6,6 +6,7 @@ import { HardDrive, Tag as TagIcon, Folders, + Database, } from "@phosphor-icons/react"; import { Location } from "@sd/assets/icons"; import type { @@ -65,6 +66,16 @@ export function isPathItem(t: ItemType): t is { Path: { sd_path: SdPath } } { return typeof t === "object" && "Path" in t; } +export function isSourcesItem(t: ItemType): t is "Sources" { + return t === "Sources"; +} + +export function isSourceItem( + t: ItemType, +): t is { Source: { source_id: string } } { + return typeof t === "object" && "Source" in t; +} + // Check if item is a "raw" location (legacy format with name/sd_path but no item_type) export function isRawLocation( item: SpaceItemType | Record, @@ -78,10 +89,12 @@ function getItemIcon(itemType: ItemType): IconData { if (isRecentsItem(itemType)) return { type: "component", icon: Clock }; if (isFavoritesItem(itemType)) return { type: "component", icon: Heart }; if (isFileKindsItem(itemType)) return { type: "component", icon: Folders }; + if (isSourcesItem(itemType)) return { type: "component", icon: Database }; if (isLocationItem(itemType)) return { type: "image", icon: Location }; if (isVolumeItem(itemType)) return { type: "component", icon: HardDrive }; if (isTagItem(itemType)) return { type: "component", icon: TagIcon }; if (isPathItem(itemType)) return { type: "image", icon: Location }; + if (isSourceItem(itemType)) return { type: "component", icon: Database }; return { type: "image", icon: Location }; } @@ -91,6 +104,7 @@ function getItemLabel(itemType: ItemType, resolvedFile?: File | null): string { if (isRecentsItem(itemType)) return "Recents"; if (isFavoritesItem(itemType)) return "Favorites"; if (isFileKindsItem(itemType)) return "File Kinds"; + if (isSourcesItem(itemType)) return "Sources"; if (isLocationItem(itemType)) return itemType.Location.name || "Unnamed Location"; if (isVolumeItem(itemType)) return itemType.Volume.name || "Unnamed Volume"; if (isTagItem(itemType)) return itemType.Tag.name || "Unnamed Tag"; @@ -106,6 +120,7 @@ function getItemLabel(itemType: ItemType, resolvedFile?: File | null): string { } return "Path"; } + if (isSourceItem(itemType)) return "Source"; return "Unknown"; } @@ -119,6 +134,7 @@ function getItemPath( if (isRecentsItem(itemType)) return "/recents"; if (isFavoritesItem(itemType)) return "/favorites"; if (isFileKindsItem(itemType)) return "/file-kinds"; + if (isSourcesItem(itemType)) return "/sources"; if (isLocationItem(itemType)) { // Use explorer route with location's SD path (passed from item.sd_path) @@ -151,6 +167,10 @@ function getItemPath( return `/explorer?path=${encodeURIComponent(JSON.stringify(itemType.Path.sd_path))}`; } + if (isSourceItem(itemType)) { + return `/sources/${itemType.Source.source_id}`; + } + return null; } diff --git a/packages/interface/src/components/TabManager/TabNavigationSync.tsx b/packages/interface/src/components/TabManager/TabNavigationSync.tsx index 1bcdbda86..ce7fead72 100644 --- a/packages/interface/src/components/TabManager/TabNavigationSync.tsx +++ b/packages/interface/src/components/TabManager/TabNavigationSync.tsx @@ -5,13 +5,15 @@ import { useTabManager } from "./useTabManager"; /** * Derives a tab title from the current route pathname and search params */ -function deriveTitleFromPath(pathname: string, search: string): string { +function deriveTitleFromPath(pathname: string, search: string): string | null { // Static route mappings const routeTitles: Record = { "/": "Overview", "/favorites": "Favorites", "/recents": "Recents", "/file-kinds": "File Kinds", + "/sources": "Sources", + "/sources/adapters": "Adapters", "/search": "Search", "/jobs": "Jobs", "/daemon": "Daemon", @@ -28,6 +30,12 @@ function deriveTitleFromPath(pathname: string, search: string): string { return tagId ? `Tag: ${tagId.slice(0, 8)}...` : "Tag"; } + // Handle source detail routes: /sources/:sourceId + // Title is set dynamically by SourceDetail component, return null to skip overwrite + if (pathname.startsWith("/sources/") && pathname !== "/sources/adapters") { + return null; + } + // Handle explorer routes if (pathname === "/explorer" && search) { const params = new URLSearchParams(search); @@ -91,9 +99,9 @@ export function TabNavigationSync() { updateTabPath(activeTabId, currentPath); } - // Always update title based on current location + // Update title based on current location (null = managed by the route component) const newTitle = deriveTitleFromPath(location.pathname, location.search); - if (activeTab && newTitle !== activeTab.title) { + if (activeTab && newTitle !== null && newTitle !== activeTab.title) { updateTabTitle(activeTabId, newTitle); } }, [currentPath, activeTab, activeTabId, updateTabPath, updateTabTitle, location.pathname, location.search]); diff --git a/packages/interface/src/hooks/useAdapterIcons.ts b/packages/interface/src/hooks/useAdapterIcons.ts new file mode 100644 index 000000000..51341b9e2 --- /dev/null +++ b/packages/interface/src/hooks/useAdapterIcons.ts @@ -0,0 +1,23 @@ +import { useLibraryQuery } from "../contexts/SpacedriveContext"; +import { useCallback } from "react"; + +/** + * Hook that provides adapter icon lookup by adapter ID. + * Fetches the adapters list once and caches via React Query. + */ +export function useAdapterIcons() { + const { data: adapters } = useLibraryQuery({ + type: "adapters.list", + input: {}, + }); + + const getIcon = useCallback( + (adapterId: string): string | null => { + if (!adapters) return null; + return adapters.find((a) => a.id === adapterId)?.icon_svg ?? null; + }, + [adapters], + ); + + return { getIcon }; +} diff --git a/packages/interface/src/router.tsx b/packages/interface/src/router.tsx index 7903c217a..938b9dfb6 100644 --- a/packages/interface/src/router.tsx +++ b/packages/interface/src/router.tsx @@ -7,6 +7,9 @@ import { DaemonManager } from "./routes/daemon"; import { TagView } from "./routes/tag"; import { FileKindsView } from "./routes/file-kinds"; import { RecentsView } from "./routes/explorer/views/RecentsView"; +import { SourcesHome } from "./routes/sources"; +import { SourceDetail } from "./routes/sources/SourceDetail"; +import { AdaptersScreen } from "./routes/sources/Adapters"; import { SpacebotProvider } from "./Spacebot/SpacebotContext"; import { SpacebotLayout } from "./Spacebot/SpacebotLayout"; import { ChatRoute } from "./Spacebot/routes/ChatRoute"; @@ -63,6 +66,18 @@ export const explorerRoutes = [ path: "tag/:tagId", element: , }, + { + path: "sources", + element: , + }, + { + path: "sources/adapters", + element: , + }, + { + path: "sources/:sourceId", + element: , + }, { path: "search", element: ( diff --git a/packages/interface/src/routes/sources/Adapters.tsx b/packages/interface/src/routes/sources/Adapters.tsx new file mode 100644 index 000000000..3d5712bb5 --- /dev/null +++ b/packages/interface/src/routes/sources/Adapters.tsx @@ -0,0 +1,383 @@ +import { useState, useCallback, useMemo } from "react"; +import { useNavigate } from "react-router-dom"; +import { + ArrowLeft, + ArrowRight, + FolderOpen, +} from "@phosphor-icons/react"; +import { + useLibraryQuery, + useLibraryMutation, +} from "../../contexts/SpacedriveContext"; +import { TopBarPortal, TopBarItem } from "../../TopBar"; +import { CircleButton } from "@spaceui/primitives"; +import { ExpandableSearchButton } from "../explorer/components/ExpandableSearchButton"; +import { SourceTypeIcon } from "../../components/Sources/SourceTypeIcon"; + +export function AdaptersScreen() { + const navigate = useNavigate(); + const [searchValue, setSearchValue] = useState(""); + + const { data: adapters, isLoading } = useLibraryQuery({ + type: "adapters.list", + input: {}, + }); + + const [selectedAdapter, setSelectedAdapter] = useState(null); + const selected = adapters?.find((a) => a.id === selectedAdapter); + + const filteredAdapters = useMemo(() => { + if (!adapters || !searchValue.trim()) return adapters; + const q = searchValue.toLowerCase(); + return adapters.filter( + (a) => + a.name.toLowerCase().includes(q) || + a.description.toLowerCase().includes(q) || + a.data_type.toLowerCase().includes(q), + ); + }, [adapters, searchValue]); + + return ( + <> + + + navigate(-1)} + /> + + + + Data Adapters + + + + } + right={ + <> + + setSearchValue("")} + /> + + + { + /* TODO: Install from directory */ + }} + title="Install adapter from directory" + /> + + + } + /> +
+ {isLoading && ( +
+
Loading...
+
+ )} + + {adapters && adapters.length === 0 && !searchValue && ( +
+

No adapters installed

+

+ Adapters are loaded from the adapters directory on startup +

+
+ )} + + {filteredAdapters && filteredAdapters.length === 0 && searchValue && ( +
+

No matching adapters

+
+ )} + + {filteredAdapters && filteredAdapters.length > 0 && ( +
+ {filteredAdapters.map((adapter) => ( + + ))} +
+ )} + + {/* Configure dialog */} + {selected && ( +
+
+
+ +

+ {selected.name} +

+
+ setSelectedAdapter(null)} + /> +
+
+ )} +
+ + ); +} + +function ConfigureForm({ + adapterId, + adapterName, + onDone, +}: { + adapterId: string; + adapterName: string; + onDone: () => void; +}) { + const navigate = useNavigate(); + + const { + data: fields, + isLoading, + error: queryError, + } = useLibraryQuery({ + type: "adapters.config", + input: { adapter_id: adapterId }, + }); + + const createSource = useLibraryMutation("sources.create"); + const syncSource = useLibraryMutation("sources.sync"); + + const [name, setName] = useState(""); + const [values, setValues] = useState>({}); + const [error, setError] = useState(null); + const [syncing, setSyncing] = useState(false); + const [result, setResult] = useState(null); + + const handleFieldChange = useCallback((key: string, value: string) => { + setValues((prev) => ({ ...prev, [key]: value })); + }, []); + + const handleSubmit = async () => { + if (!name.trim()) { + setError("Source name is required"); + return; + } + + try { + const config: Record = {}; + for (const field of fields ?? []) { + const val = values[field.key]?.trim() ?? ""; + if (field.required && !val) { + setError(`${field.name} is required`); + return; + } + if (!val && !field.required) continue; + + if (field.field_type === "integer" && val) { + config[field.key] = parseInt(val, 10); + } else if (field.field_type === "boolean") { + config[field.key] = val === "true"; + } else { + config[field.key] = val; + } + } + + const source = await createSource.mutateAsync({ + name: name.trim(), + adapter_id: adapterId, + config, + }); + + setSyncing(true); + const report = await syncSource.mutateAsync({ + source_id: source.id, + }); + + if (report.error) { + setResult(`Synced with warning: ${report.error}`); + } else { + setResult( + `Synced ${report.records_upserted} records in ${(report.duration_ms / 1000).toFixed(1)}s`, + ); + } + setSyncing(false); + } catch (e) { + setError(String(e)); + setSyncing(false); + } + }; + + if (isLoading) { + return ( +
+ Loading... +
+ ); + } + + if (queryError) { + return ( +
+ Failed to load config: {String(queryError)} +
+ ); + } + + if (result) { + const isError = result.startsWith("Sync failed"); + return ( +
+

+ {isError ? "Something went wrong" : "Source added"} +

+

{result}

+ +
+ ); + } + + if (syncing) { + return ( +
+
+

Syncing...

+
+ ); + } + + return ( +
+
+ + setName(e.target.value)} + placeholder={`e.g., My ${adapterName}`} + className="border-app-line bg-app-input text-ink w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-accent" + /> +
+ + {fields?.map((field) => ( +
+ + {field.description && ( +

+ {field.description} +

+ )} + + handleFieldChange(field.key, e.target.value) + } + placeholder={ + field.default + ? `Default: ${field.default}` + : undefined + } + className="border-app-line bg-app-input text-ink w-full rounded-md border px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-accent" + /> +
+ ))} + + {error && ( +
+ {error} +
+ )} + +
+ + +
+
+ ); +} diff --git a/packages/interface/src/routes/sources/SourceDetail.tsx b/packages/interface/src/routes/sources/SourceDetail.tsx new file mode 100644 index 000000000..c1690a2ed --- /dev/null +++ b/packages/interface/src/routes/sources/SourceDetail.tsx @@ -0,0 +1,484 @@ +import { useParams, useNavigate } from "react-router-dom"; +import { useState, useEffect, useMemo, useRef, useCallback } from "react"; +import { useVirtualizer } from "@tanstack/react-virtual"; +import { + ArrowLeft, + ArrowsClockwise, + DotsThree, + Trash, + ArrowSquareUp, +} from "@phosphor-icons/react"; +import { + useLibraryQuery, + useLibraryMutation, +} from "../../contexts/SpacedriveContext"; +import { useTabManager } from "../../components/TabManager/useTabManager"; +import { TopBarPortal, TopBarItem } from "../../TopBar"; +import { CircleButton, Popover, usePopover } from "@spaceui/primitives"; +import { ExpandableSearchButton } from "../explorer/components/ExpandableSearchButton"; +import { SourcePathBar } from "../../components/Sources/SourcePathBar"; +import { SourceDataRow } from "../../components/Sources/SourceDataRow"; + +const PAGE_SIZE = 100; + +export function SourceDetail() { + const { sourceId } = useParams<{ sourceId: string }>(); + const navigate = useNavigate(); + const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); + const [searchValue, setSearchValue] = useState(""); + const scrollRef = useRef(null); + + // Infinite scroll state + const [allItems, setAllItems] = useState< + Array<{ + id: string; + external_id: string; + title: string; + preview: string | null; + subtitle: string | null; + }> + >([]); + const [offset, setOffset] = useState(0); + const [hasMore, setHasMore] = useState(true); + const [loadingMore, setLoadingMore] = useState(false); + + const { + data: source, + isLoading, + error, + } = useLibraryQuery({ + type: "sources.get", + input: { source_id: sourceId ?? "" }, + }); + + // Initial page + const { data: initialItems, isLoading: itemsLoading } = useLibraryQuery({ + type: "sources.list_items", + input: { source_id: sourceId ?? "", limit: PAGE_SIZE, offset: 0 }, + }); + + // Seed allItems from initial fetch + useEffect(() => { + if (initialItems) { + setAllItems(initialItems); + setOffset(initialItems.length); + setHasMore(initialItems.length >= PAGE_SIZE); + } + }, [initialItems]); + + // Reset when sourceId changes + useEffect(() => { + setAllItems([]); + setOffset(0); + setHasMore(true); + }, [sourceId]); + + const { data: adapters } = useLibraryQuery({ + type: "adapters.list", + input: {}, + }); + + const syncMutation = useLibraryMutation("sources.sync"); + const deleteMutation = useLibraryMutation("sources.delete"); + const updateMutation = useLibraryMutation("adapters.update"); + + const adapterHasUpdate = + adapters?.find((a) => a.id === source?.adapter_id)?.update_available ?? + false; + + // Sync tab title with source name + const { activeTabId, updateTabTitle } = useTabManager(); + useEffect(() => { + if (source?.name) { + updateTabTitle(activeTabId, source.name); + } + }, [source?.name, activeTabId, updateTabTitle]); + + // Filter items by search + const filteredItems = useMemo(() => { + if (!searchValue.trim()) return allItems; + const q = searchValue.toLowerCase(); + return allItems.filter( + (item) => + item.title.toLowerCase().includes(q) || + item.subtitle?.toLowerCase().includes(q) || + item.preview?.toLowerCase().includes(q), + ); + }, [allItems, searchValue]); + + // Virtualizer + const virtualizer = useVirtualizer({ + count: filteredItems.length, + getScrollElement: () => scrollRef.current, + estimateSize: () => 64, + overscan: 20, + // Avoid flushSync during render (React 19 compat) + scrollToFn: (offset, { adjustments, behavior }, instance) => { + const el = instance.scrollElement; + if (!el) return; + const top = offset + (adjustments ?? 0); + el.scrollTo({ top, behavior }); + }, + }); + + // Load more when scrolling near bottom + const loadMore = useCallback(async () => { + if (loadingMore || !hasMore || !sourceId) return; + setLoadingMore(true); + // We can't use useLibraryQuery for imperative fetches, + // so we'll bump the offset and let a new query handle it + }, [loadingMore, hasMore, sourceId]); + + // Fetch next page + const { data: nextPage } = useLibraryQuery( + { + type: "sources.list_items", + input: { + source_id: sourceId ?? "", + limit: PAGE_SIZE, + offset, + }, + }, + { enabled: loadingMore && offset > 0 }, + ); + + useEffect(() => { + if (nextPage && loadingMore) { + setAllItems((prev) => [...prev, ...nextPage]); + setOffset((prev) => prev + nextPage.length); + setHasMore(nextPage.length >= PAGE_SIZE); + setLoadingMore(false); + } + }, [nextPage, loadingMore]); + + // Trigger load more when virtualizer reaches near the end + const lastVirtualItem = + virtualizer.getVirtualItems()[ + virtualizer.getVirtualItems().length - 1 + ]; + + useEffect(() => { + if ( + lastVirtualItem && + lastVirtualItem.index >= filteredItems.length - 10 && + hasMore && + !loadingMore && + !searchValue + ) { + setLoadingMore(true); + } + }, [ + lastVirtualItem?.index, + filteredItems.length, + hasMore, + loadingMore, + searchValue, + ]); + + if (isLoading) { + return ( +
+
Loading...
+
+ ); + } + + if (error || !source) { + return ( +
+
+

+ Failed to load source:{" "} + {error ? String(error) : "Not found"} +

+
+
+ ); + } + + return ( +
+ + + navigate("/sources")} + /> + + + + + + } + right={ + <> + + setSearchValue("")} + /> + + + + syncMutation.mutate({ + source_id: source.id, + }) + } + title="Sync" + active={ + syncMutation.isPending || + source.status === "syncing" + } + /> + + + + updateMutation.mutate({ + adapter_id: source.adapter_id, + }) + } + isUpdating={updateMutation.isPending} + onDelete={() => setShowDeleteConfirm(true)} + /> + + + } + /> + + {/* Banners */} + {(updateMutation.data || + updateMutation.error || + syncMutation.error || + (syncMutation.data && !syncMutation.data.error)) && ( +
+ {updateMutation.data && ( +
+

+ Updated {updateMutation.data.adapter_id}: v + {updateMutation.data.old_version} → v + {updateMutation.data.new_version} + {updateMutation.data.schema_changed + ? " (schema changed — will migrate on next sync)" + : ""} +

+
+ )} + {updateMutation.error && ( +
+

+ Update failed: {String(updateMutation.error)} +

+
+ )} + {syncMutation.error && ( +
+

+ Sync failed: {String(syncMutation.error)} +

+
+ )} + {syncMutation.data && !syncMutation.data.error && ( +
+

+ Synced{" "} + {syncMutation.data.records_upserted} records in{" "} + {( + syncMutation.data.duration_ms / 1000 + ).toFixed(1)} + s +

+
+ )} +
+ )} + + {/* Virtualized items list */} +
+ {itemsLoading && ( +
+ Loading items... +
+ )} + + {!itemsLoading && filteredItems.length === 0 && ( +
+ {searchValue + ? "No matching items" + : "No items yet. Run a sync to populate."} +
+ )} + + {filteredItems.length > 0 && ( +
+ {virtualizer.getVirtualItems().map((virtualRow) => { + const item = filteredItems[virtualRow.index]; + if (!item) return null; + return ( +
+ +
+ ); + })} +
+ )} + + {loadingMore && ( +
+ Loading more... +
+ )} +
+ + {/* Delete confirmation */} + {showDeleteConfirm && ( +
+
+

+ Delete source +

+

+ This will permanently delete{" "} + + {source.name} + {" "} + and all its data. +

+ {deleteMutation.error && ( +

+ {String(deleteMutation.error)} +

+ )} +
+ + +
+
+
+ )} +
+ ); +} + +function MoreActionsMenu({ + adapterHasUpdate, + onUpdate, + isUpdating, + onDelete, +}: { + adapterHasUpdate: boolean; + onUpdate: () => void; + isUpdating: boolean; + onDelete: () => void; +}) { + const popover = usePopover(); + + return ( + + + + + + {adapterHasUpdate && ( + + )} + + + + ); +} diff --git a/packages/interface/src/routes/sources/index.tsx b/packages/interface/src/routes/sources/index.tsx new file mode 100644 index 000000000..7477313f0 --- /dev/null +++ b/packages/interface/src/routes/sources/index.tsx @@ -0,0 +1,98 @@ +import { Plus, ArrowLeft } from "@phosphor-icons/react"; +import { useNavigate } from "react-router-dom"; +import { useLibraryQuery } from "../../contexts/SpacedriveContext"; +import { useTabManager } from "../../components/TabManager/useTabManager"; +import { SourceCard } from "../../components/Sources/SourceCard"; +import { TopBarPortal, TopBarItem } from "../../TopBar"; +import { CircleButton } from "@spaceui/primitives"; +import { SearchBar } from "@spaceui/primitives"; + +export function SourcesHome() { + const navigate = useNavigate(); + const { createTab } = useTabManager(); + const { data: sources, isLoading, error } = useLibraryQuery({ + type: "sources.list", + input: { data_type: null }, + }); + + return ( + <> + + + navigate(-1)} + /> + + +

+ Sources +

+
+ + } + right={ + <> + + {}} + onClear={() => {}} + className="w-64" + /> + + + createTab("Adapters", "/sources/adapters")} + title="Add Source" + /> + + + } + /> +
+ + {isLoading && ( +
+
Loading...
+
+ )} + + {error && ( +
+

+ Failed to load sources: {String(error)} +

+
+ )} + + {sources && sources.length === 0 && ( +
+

No sources yet

+

+ Add a data source to get started +

+ +
+ )} + + {sources && sources.length > 0 && ( +
+ {sources.map((source) => ( + + ))} +
+ )} +
+ + ); +} diff --git a/packages/ts-client/src/generated/types.ts b/packages/ts-client/src/generated/types.ts index 486673941..98cc53928 100644 --- a/packages/ts-client/src/generated/types.ts +++ b/packages/ts-client/src/generated/types.ts @@ -14,6 +14,10 @@ export type ActiveJobsInput = Record; export type ActiveJobsOutput = { jobs: ActiveJobItem[]; running_count: number; paused_count: number }; +export type AdapterConfigField = { key: string; name: string; description: string; field_type: string; required: boolean; secret: boolean; default: string | null }; + +export type AdapterInfo = { id: string; name: string; description: string; version: string; author: string; data_type: string; icon_svg: string | null; update_available: boolean }; + export type AddGroupInput = { space_id: string; name: string; group_type: GroupType }; export type AddGroupOutput = { group: SpaceGroup }; @@ -476,6 +480,44 @@ folder_path: SdPath; */ job_receipt?: JobReceipt | null }; +/** + * Input for creating a new archive source + */ +export type CreateSourceInput = { +/** + * Display name for the source + */ +name: string; +/** + * Adapter ID (e.g., "gmail", "obsidian", "chrome-bookmarks") + */ +adapter_id: string; +/** + * Adapter-specific configuration + */ +config: JsonValue }; + +/** + * Output from creating a new archive source + */ +export type CreateSourceOutput = { +/** + * The ID of the newly created source + */ +id: string; +/** + * The display name of the source + */ +name: string; +/** + * The adapter ID used + */ +adapter_id: string; +/** + * Current status (usually "idle" initially) + */ +status: string }; + export type CreateTagInput = { /** * The canonical name for this tag @@ -551,6 +593,10 @@ export type DeleteItemInput = { item_id: string }; export type DeleteItemOutput = { success: boolean }; +export type DeleteSourceInput = { source_id: string }; + +export type DeleteSourceOutput = { deleted: boolean }; + export type DeleteWhisperModelInput = { model: string }; export type DeleteWhisperModelOutput = { deleted: boolean }; @@ -1528,6 +1574,8 @@ completion: ProgressCompletion; */ performance: PerformanceMetrics }; +export type GetAdapterConfigInput = { adapter_id: string }; + /** * Input for getting app configuration */ @@ -1538,6 +1586,8 @@ export type GetAppConfigQueryInput = null; */ export type GetLibraryConfigQueryInput = null; +export type GetSourceInput = { source_id: string }; + /** * Input for getting sync activity summary */ @@ -1660,6 +1710,10 @@ export type GroupType = * Tag collection */ "Tags" | +/** + * Archive data sources (email, notes, bookmarks, etc.) + */ +"Sources" | /** * Cloud storage providers */ @@ -2029,7 +2083,15 @@ export type ItemType = /** * Any arbitrary path (dragged from explorer) */ -{ Path: { sd_path: SdPath } }; +{ Path: { sd_path: SdPath } } | +/** + * All archive data sources screen + */ +"Sources" | +/** + * Specific archive data source + */ +{ Source: { source_id: string } }; export type JobCancelInput = { job_id: string }; @@ -2583,6 +2645,8 @@ devicesRegistered: boolean; */ message: string }; +export type ListAdaptersInput = Record; + export type ListEventsInput = Record; export type ListEventsOutput = { @@ -2645,6 +2709,14 @@ total: number; */ connected: number }; +export type ListSourceItemsInput = { source_id: string; limit: number; offset: number }; + +export type ListSourcesInput = { +/** + * Filter by data type + */ +data_type: string | null }; + export type ListWhisperModelsInput = Record; export type ListWhisperModelsOutput = { models: ModelInfo[]; total_downloaded_size: number }; @@ -3635,6 +3707,41 @@ export type SortField = "Relevance" | "Name" | "Size" | "ModifiedAt" | "CreatedA */ export type SortOptions = { field: SortField; direction: SortDirection }; +/** + * Information about a source + */ +export type SourceInfo = { +/** + * Source ID + */ +id: string; +/** + * Display name + */ +name: string; +/** + * Data type (e.g., "email", "bookmark", "note") + */ +data_type: string; +/** + * Adapter ID + */ +adapter_id: string; +/** + * Number of items + */ +item_count: number; +/** + * Last sync timestamp + */ +last_synced: string | null; +/** + * Current status + */ +status: string }; + +export type SourceItem = { id: string; external_id: string; title: string; preview: string | null; subtitle: string | null }; + /** * A Space defines a sidebar layout and filtering context */ @@ -3927,6 +4034,10 @@ export type SyncPartnerInfo = { device_uuid: string; device_name: string; is_pai export type SyncPartnersDebugInfo = { total_devices: number; sync_enabled_devices: number; paired_devices: number; final_sync_partners: number; device_details: DeviceDebugInfo[] }; +export type SyncSourceInput = { source_id: string }; + +export type SyncSourceOutput = { records_upserted: number; records_deleted: number; duration_ms: number; error: string | null }; + /** * State metrics snapshot */ @@ -4189,6 +4300,10 @@ total_count: number; */ total_size: number }; +export type UpdateAdapterInput = { adapter_id: string }; + +export type UpdateAdapterOutput = { adapter_id: string; old_version: string; new_version: string; schema_changed: boolean }; + /** * Input for updating app configuration * All fields are optional for partial updates @@ -4793,7 +4908,8 @@ export type CoreAction = ; export type LibraryAction = - { type: 'config.library.update'; input: UpdateLibraryConfigInput; output: UpdateLibraryConfigOutput } + { type: 'adapters.update'; input: UpdateAdapterInput; output: UpdateAdapterOutput } + | { type: 'config.library.update'; input: UpdateLibraryConfigInput; output: UpdateLibraryConfigOutput } | { type: 'files.copy'; input: FileCopyInput; output: JobReceipt } | { type: 'files.createFolder'; input: CreateFolderInput; output: CreateFolderOutput } | { type: 'files.delete'; input: FileDeleteInput; output: JobReceipt } @@ -4820,6 +4936,9 @@ export type LibraryAction = | { type: 'media.thumbnail'; input: ThumbnailInput; output: JobReceipt } | { type: 'media.thumbnail.regenerate'; input: RegenerateThumbnailInput; output: RegenerateThumbnailOutput } | { type: 'media.thumbstrip.generate'; input: GenerateThumbstripInput; output: GenerateThumbstripOutput } + | { type: 'sources.create'; input: CreateSourceInput; output: CreateSourceOutput } + | { type: 'sources.delete'; input: DeleteSourceInput; output: DeleteSourceOutput } + | { type: 'sources.sync'; input: SyncSourceInput; output: SyncSourceOutput } | { type: 'spaces.add_group'; input: AddGroupInput; output: AddGroupOutput } | { type: 'spaces.add_item'; input: AddItemInput; output: AddItemOutput } | { type: 'spaces.create'; input: SpaceCreateInput; output: SpaceCreateOutput } @@ -4859,7 +4978,9 @@ export type CoreQuery = ; export type LibraryQuery = - { type: 'config.library.get'; input: GetLibraryConfigQueryInput; output: LibrarySettingsOutput } + { type: 'adapters.config'; input: GetAdapterConfigInput; output: [AdapterConfigField] } + | { type: 'adapters.list'; input: ListAdaptersInput; output: [AdapterInfo] } + | { type: 'config.library.get'; input: GetLibraryConfigQueryInput; output: LibrarySettingsOutput } | { type: 'devices.list'; input: ListLibraryDevicesInput; output: [Device] } | { type: 'files.alternate_instances'; input: AlternateInstancesInput; output: AlternateInstancesOutput } | { type: 'files.by_id'; input: FileByIdQuery; output: File } @@ -4877,6 +4998,9 @@ export type LibraryQuery = | { type: 'locations.suggested'; input: SuggestedLocationsQueryInput; output: SuggestedLocationsOutput } | { type: 'locations.validate_path'; input: ValidateLocationPathInput; output: ValidateLocationPathOutput } | { type: 'search.files'; input: FileSearchInput; output: FileSearchOutput } + | { type: 'sources.get'; input: GetSourceInput; output: SourceInfo } + | { type: 'sources.list'; input: ListSourcesInput; output: [SourceInfo] } + | { type: 'sources.list_items'; input: ListSourceItemsInput; output: [SourceItem] } | { type: 'spaces.get'; input: SpaceGetQueryInput; output: SpaceGetOutput } | { type: 'spaces.get_layout'; input: SpaceLayoutQueryInput; output: SpaceLayout } | { type: 'spaces.list'; input: SpacesListQueryInput; output: SpacesListOutput } @@ -4915,6 +5039,7 @@ export const WIRE_METHODS = { }, libraryActions: { + 'adapters.update': 'action:adapters.update.input', 'config.library.update': 'action:config.library.update.input', 'files.copy': 'action:files.copy.input', 'files.createFolder': 'action:files.createFolder.input', @@ -4942,6 +5067,9 @@ export const WIRE_METHODS = { 'media.thumbnail': 'action:media.thumbnail.input', 'media.thumbnail.regenerate': 'action:media.thumbnail.regenerate.input', 'media.thumbstrip.generate': 'action:media.thumbstrip.generate.input', + 'sources.create': 'action:sources.create.input', + 'sources.delete': 'action:sources.delete.input', + 'sources.sync': 'action:sources.sync.input', 'spaces.add_group': 'action:spaces.add_group.input', 'spaces.add_item': 'action:spaces.add_item.input', 'spaces.create': 'action:spaces.create.input', @@ -4981,6 +5109,8 @@ export const WIRE_METHODS = { }, libraryQueries: { + 'adapters.config': 'query:adapters.config', + 'adapters.list': 'query:adapters.list', 'config.library.get': 'query:config.library.get', 'devices.list': 'query:devices.list', 'files.alternate_instances': 'query:files.alternate_instances', @@ -4999,6 +5129,9 @@ export const WIRE_METHODS = { 'locations.suggested': 'query:locations.suggested', 'locations.validate_path': 'query:locations.validate_path', 'search.files': 'query:search.files', + 'sources.get': 'query:sources.get', + 'sources.list': 'query:sources.list', + 'sources.list_items': 'query:sources.list_items', 'spaces.get': 'query:spaces.get', 'spaces.get_layout': 'query:spaces.get_layout', 'spaces.list': 'query:spaces.list',