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) <noreply@anthropic.com>
This commit is contained in:
James Pine
2026-03-29 03:24:53 -07:00
parent 9f0685abc9
commit 907ab4e267
43 changed files with 2737 additions and 218 deletions

View File

@@ -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

View File

@@ -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;

219
core/src/data/manager.rs Normal file
View File

@@ -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<Self, String> {
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<Vec<sd_archive::SourceInfo>, 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<sd_archive::SourceInfo, String> {
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<sd_archive::SyncReport, String> {
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<Vec<sd_archive::db::ItemRow>, 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<sd_archive::AdapterInfo> {
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<sd_archive::AdapterUpdateResult, String> {
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<Vec<sd_archive::adapter::script::ConfigField>, 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(())
}

5
core/src/data/mod.rs Normal file
View File

@@ -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;

View File

@@ -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)]

View File

@@ -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(())
}

View File

@@ -0,0 +1,5 @@
//! Get adapter config fields
pub mod query;
pub use query::*;

View File

@@ -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<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GetAdapterConfigQuery {
pub input: GetAdapterConfigInput,
}
impl LibraryQuery for GetAdapterConfigQuery {
type Input = GetAdapterConfigInput;
type Output = Vec<AdapterConfigField>;
fn from_input(input: Self::Input) -> QueryResult<Self> {
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<CoreContext>,
session: crate::infra::api::SessionContext,
) -> QueryResult<Self::Output> {
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");

View File

@@ -0,0 +1,5 @@
//! List available adapters
pub mod query;
pub use query::*;

View File

@@ -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<String>,
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<AdapterInfo>;
fn from_input(input: Self::Input) -> QueryResult<Self> {
Ok(Self { input })
}
async fn execute(
self,
context: Arc<CoreContext>,
session: crate::infra::api::SessionContext,
) -> QueryResult<Self::Output> {
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");

View File

@@ -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::*;

View File

@@ -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<Self, String> {
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<Library>,
_context: Arc<CoreContext>,
) -> Result<Self::Output, ActionError> {
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");

View File

@@ -0,0 +1,5 @@
//! Update an adapter
pub mod action;
pub use action::*;

View File

@@ -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;

View File

@@ -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<Self, String> {
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<Library>,
_context: Arc<CoreContext>,
) -> Result<Self::Output, ActionError> {
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");

View File

@@ -0,0 +1,5 @@
//! Source deletion action
pub mod action;
pub use action::*;

View File

@@ -0,0 +1,5 @@
//! Get a single source by ID
pub mod query;
pub use query::*;

View File

@@ -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<Self> {
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<CoreContext>,
session: crate::infra::api::SessionContext,
) -> QueryResult<Self::Output> {
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");

View File

@@ -0,0 +1,5 @@
//! Source items listing query
pub mod query;
pub use query::*;

View File

@@ -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<String>,
pub subtitle: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ListSourceItemsQuery {
pub input: ListSourceItemsInput,
}
impl LibraryQuery for ListSourceItemsQuery {
type Input = ListSourceItemsInput;
type Output = Vec<SourceItem>;
fn from_input(input: Self::Input) -> QueryResult<Self> {
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<CoreContext>,
session: crate::infra::api::SessionContext,
) -> QueryResult<Self::Output> {
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");

View File

@@ -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::*;

View File

@@ -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<String>,
}
#[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<Self, String> {
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<Library>,
_context: Arc<CoreContext>,
) -> Result<Self::Output, ActionError> {
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");

View File

@@ -0,0 +1,5 @@
//! Source sync action
pub mod action;
pub use action::*;

View File

@@ -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<bool> {
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<crate::adapter::AdapterInfo> {
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<std::path::PathBuf> {
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<crate::adapter::AdapterUpdateResult> {
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<String> {
let adapter = ScriptAdapter::from_dir(source_dir)?;

View File

@@ -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'}
];

View File

@@ -42,11 +42,11 @@ function SidebarHistoryItem({
isActive ? 'bg-sidebar-selected/40' : 'hover:bg-sidebar-box'
}`}
>
<div>
<div className="text-sidebar-ink text-sm font-medium">
<div className="min-w-0">
<div className="text-sidebar-ink truncate text-sm font-medium">
{conversation.title}
</div>
<div className="text-sidebar-inkDull line-clamp-2 text-xs">
<div className="text-ink-dull truncate text-xs">
{conversation.last_message_preview ?? 'No messages yet'}
</div>
</div>

View File

@@ -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,
});

View File

@@ -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 (
<button
onClick={() => navigate(`/sources/${source.id}`)}
className="border-app-line bg-app-box hover:border-app-line/80 hover:bg-app-hover group relative rounded-lg border p-4 text-left transition-all"
>
<div className="mb-3 flex items-center gap-3">
<SourceTypeIcon type={source.data_type} svg={getIcon(source.adapter_id)} size="md" />
<div className="min-w-0 flex-1">
<h3 className="text-ink truncate text-sm font-medium">
{source.name}
</h3>
<p className="text-ink-faint text-xs">{source.adapter_id}</p>
</div>
<SourceStatusBadge status={source.status} />
</div>
<div className="text-ink-faint flex items-center justify-between text-xs">
<span>
{source.item_count.toLocaleString()} item
{source.item_count !== 1 ? "s" : ""}
</span>
<span>
{source.last_synced
? formatRelative(source.last_synced)
: "Never synced"}
</span>
</div>
</button>
);
}

View File

@@ -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 (
<div className="hover:bg-app-hover flex min-w-0 flex-col gap-0.5 overflow-hidden rounded-lg px-3 py-2.5 transition-colors">
{/* Title row */}
<div className="flex min-w-0 items-baseline gap-2">
<span className="text-ink min-w-0 flex-1 truncate text-sm">
{title}
</span>
{date && (
<span className="text-ink-faint shrink-0 text-[11px]">
{formatDate(date)}
</span>
)}
</div>
{subtitle && (
<p className="text-ink-faint truncate text-xs">{subtitle}</p>
)}
{preview && (
<p className="text-ink-faint/70 truncate text-xs">
{cleanPreview(preview)}
</p>
)}
</div>
);
}

View File

@@ -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 (
<div
className="border-app-line/50 bg-app-overlay/80 flex h-8 items-center gap-1.5 rounded-full border px-3 backdrop-blur-xl"
>
<Database size={14} className="text-ink-faint shrink-0" />
<button
onClick={() => navigate("/sources")}
className="text-sidebar-inkDull hover:text-sidebar-ink whitespace-nowrap text-xs font-medium transition-colors"
>
Sources
</button>
<CaretRight size={12} className="text-ink-faint shrink-0 opacity-50" />
<span className="text-sidebar-ink whitespace-nowrap text-xs font-medium">
{sourceName}
</span>
<span className="text-ink-faint ml-1 whitespace-nowrap text-[11px]">
{itemCount.toLocaleString()} items
</span>
</div>
);
}

View File

@@ -0,0 +1,20 @@
interface SourceStatusBadgeProps {
status: string;
}
export function SourceStatusBadge({ status }: SourceStatusBadgeProps) {
return (
<span className="text-ink-faint inline-flex items-center gap-1.5 text-[11px] font-medium">
<span
className={`h-1.5 w-1.5 rounded-full ${
status === "syncing"
? "bg-accent animate-pulse"
: status === "error"
? "bg-red-400"
: "bg-ink-faint"
}`}
/>
{status}
</span>
);
}

View File

@@ -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<string, string> = {
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 (
<div
className={`shrink-0 rounded-lg [&>svg]:h-full [&>svg]:w-full ${SIZE_CLASSES[size]}`}
dangerouslySetInnerHTML={{ __html: svg }}
/>
);
}
const label = FALLBACK_LABELS[typeName] ?? typeName.slice(0, 4);
return (
<div
className={`bg-accent/10 text-accent flex shrink-0 items-center justify-center rounded-lg font-mono text-[10px] font-medium uppercase ${SIZE_CLASSES[size]}`}
>
{label}
</div>
);
}

View File

@@ -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 (
<div>
<GroupHeader
label="Sources"
isCollapsed={isCollapsed}
onToggle={onToggle}
sortableAttributes={sortableAttributes}
sortableListeners={sortableListeners}
/>
{!isCollapsed && (
<div className="space-y-0.5">
{sourcesList.map((source) => {
const isActive = location.pathname === `/sources/${source.id}`;
return (
<button
key={source.id}
onClick={() => navigate(`/sources/${source.id}`)}
className={`flex w-full items-center gap-2 rounded-md px-2 py-1 text-left text-sm font-medium ${
isActive
? "bg-sidebar-selected text-sidebar-ink"
: "text-sidebar-inkDull hover:text-sidebar-ink"
}`}
>
{getIcon(source.adapter_id) ? (
<div
className={`size-4 shrink-0 [&>svg]:h-full [&>svg]:w-full ${
isActive
? "opacity-100"
: "opacity-60 grayscale"
}`}
dangerouslySetInnerHTML={{
__html: getIcon(source.adapter_id)!,
}}
/>
) : (
<Database
className="size-4 shrink-0"
weight={isActive ? "fill" : "bold"}
/>
)}
<span className="truncate">{source.name}</span>
</button>
);
})}
</div>
)}
</div>
);
}

View File

@@ -31,6 +31,10 @@ const PALETTE_ITEMS: PaletteItem[] = [
type: "FileKinds",
label: "File Kinds",
},
{
type: "Sources",
label: "Sources",
},
];
function DraggablePaletteItem({ item }: { item: PaletteItem }) {

View File

@@ -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 (
<div data-group-id={group.id}>
<SourcesGroup
isCollapsed={isCollapsed}
onToggle={handleToggle}
sortableAttributes={sortableAttributes}
sortableListeners={sortableListeners}
/>
</div>
);
}
// Empty drop zone for groups with no items
const { setNodeRef: setEmptyRef, isOver: isOverEmpty } = useDroppable({
id: `group-${group.id}-empty`,

View File

@@ -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<string, unknown>,
@@ -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;
}

View File

@@ -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<string, string> = {
"/": "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]);

View File

@@ -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 };
}

View File

@@ -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: <TagView />,
},
{
path: "sources",
element: <SourcesHome />,
},
{
path: "sources/adapters",
element: <AdaptersScreen />,
},
{
path: "sources/:sourceId",
element: <SourceDetail />,
},
{
path: "search",
element: (

View File

@@ -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<string | null>(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 (
<>
<TopBarPortal
left={
<>
<TopBarItem
id="navigation"
label="Navigation"
priority="high"
>
<CircleButton
icon={ArrowLeft}
onClick={() => navigate(-1)}
/>
</TopBarItem>
<TopBarItem
id="title"
label="Title"
priority="high"
>
<span className="text-ink whitespace-nowrap text-xs font-medium">
Data Adapters
</span>
</TopBarItem>
</>
}
right={
<>
<TopBarItem
id="search"
label="Search"
priority="high"
>
<ExpandableSearchButton
placeholder="Search adapters..."
value={searchValue}
onChange={setSearchValue}
onClear={() => setSearchValue("")}
/>
</TopBarItem>
<TopBarItem
id="install"
label="Install"
priority="high"
>
<CircleButton
icon={FolderOpen}
onClick={() => {
/* TODO: Install from directory */
}}
title="Install adapter from directory"
/>
</TopBarItem>
</>
}
/>
<div className="p-6">
{isLoading && (
<div className="flex items-center justify-center py-20">
<div className="text-ink-faint text-sm">Loading...</div>
</div>
)}
{adapters && adapters.length === 0 && !searchValue && (
<div className="flex flex-col items-center justify-center py-20">
<p className="text-ink-dull text-sm">No adapters installed</p>
<p className="text-ink-faint mt-1 text-xs">
Adapters are loaded from the adapters directory on startup
</p>
</div>
)}
{filteredAdapters && filteredAdapters.length === 0 && searchValue && (
<div className="flex items-center justify-center py-20">
<p className="text-ink-faint text-sm">No matching adapters</p>
</div>
)}
{filteredAdapters && filteredAdapters.length > 0 && (
<div className="grid grid-cols-2 gap-3 lg:grid-cols-3">
{filteredAdapters.map((adapter) => (
<button
key={adapter.id}
onClick={() => setSelectedAdapter(adapter.id)}
className="border-app-line bg-app-box hover:border-app-line/80 hover:bg-app-hover flex items-center gap-3 rounded-lg border p-3 text-left transition-all"
>
<SourceTypeIcon
type={adapter.data_type}
svg={adapter.icon_svg}
size="md"
/>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<span className="text-ink truncate text-sm font-medium">
{adapter.name}
</span>
<span className="bg-app-line/60 text-ink-faint shrink-0 rounded px-1.5 py-0.5 text-[10px]">
v{adapter.version}
</span>
{adapter.update_available && (
<span className="shrink-0 rounded-full bg-blue-500/15 px-2 py-0.5 text-[10px] font-medium text-blue-400 ring-1 ring-blue-500/30">
Update
</span>
)}
</div>
<div className="text-ink-faint mt-0.5 truncate text-xs">
{adapter.description || adapter.data_type}
</div>
{adapter.author && (
<div className="text-ink-faint/70 mt-0.5 text-[11px]">
by {adapter.author}
</div>
)}
</div>
</button>
))}
</div>
)}
{/* Configure dialog */}
{selected && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="border-app-line bg-app-box w-full max-w-md rounded-xl border p-6 shadow-2xl">
<div className="mb-4 flex items-center gap-3">
<SourceTypeIcon
type={selected.data_type}
svg={selected.icon_svg}
size="sm"
/>
<h3 className="text-ink text-sm font-semibold">
{selected.name}
</h3>
</div>
<ConfigureForm
adapterId={selected.id}
adapterName={selected.name}
onDone={() => setSelectedAdapter(null)}
/>
</div>
</div>
)}
</div>
</>
);
}
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<Record<string, string>>({});
const [error, setError] = useState<string | null>(null);
const [syncing, setSyncing] = useState(false);
const [result, setResult] = useState<string | null>(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<string, unknown> = {};
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 (
<div className="text-ink-faint py-8 text-center text-sm">
Loading...
</div>
);
}
if (queryError) {
return (
<div className="rounded-md border border-red-400/20 p-3 text-xs text-red-400">
Failed to load config: {String(queryError)}
</div>
);
}
if (result) {
const isError = result.startsWith("Sync failed");
return (
<div className="flex flex-col items-center py-6">
<p
className={`text-sm ${isError ? "text-red-400" : "text-ink"}`}
>
{isError ? "Something went wrong" : "Source added"}
</p>
<p className="text-ink-faint mt-1 text-xs">{result}</p>
<button
onClick={() => {
onDone();
navigate("/sources");
}}
className="bg-accent hover:bg-accent-deep mt-4 rounded-lg px-3.5 py-1.5 text-sm font-medium text-white transition-colors"
>
Done
</button>
</div>
);
}
if (syncing) {
return (
<div className="flex flex-col items-center py-6">
<div className="border-accent mb-3 h-5 w-5 animate-spin rounded-full border-2 border-t-transparent" />
<p className="text-ink-dull text-sm">Syncing...</p>
</div>
);
}
return (
<div className="flex flex-col gap-3">
<div>
<label className="text-ink-dull mb-1 block text-xs font-medium">
Source Name
</label>
<input
value={name}
onChange={(e) => 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"
/>
</div>
{fields?.map((field) => (
<div key={field.key}>
<label className="text-ink-dull mb-1 block text-xs font-medium">
{field.name}
{field.required && (
<span className="ml-1 text-red-400">*</span>
)}
</label>
{field.description && (
<p className="text-ink-faint mb-1 text-[11px] leading-relaxed">
{field.description}
</p>
)}
<input
type={field.secret ? "password" : "text"}
value={values[field.key] ?? ""}
onChange={(e) =>
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"
/>
</div>
))}
{error && (
<div className="rounded-md border border-red-400/20 p-2 text-xs text-red-400">
{error}
</div>
)}
<div className="mt-1 flex gap-2">
<button
onClick={handleSubmit}
disabled={createSource.isPending}
className="bg-accent hover:bg-accent-deep flex-1 rounded-lg px-3.5 py-1.5 text-sm font-medium text-white transition-colors disabled:opacity-40"
>
Add & Sync
</button>
<button
onClick={onDone}
className="text-ink-faint hover:text-ink rounded-lg px-3.5 py-1.5 text-sm font-medium transition-colors"
>
Cancel
</button>
</div>
</div>
);
}

View File

@@ -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<HTMLDivElement>(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 (
<div className="flex items-center justify-center py-20">
<div className="text-ink-faint text-sm">Loading...</div>
</div>
);
}
if (error || !source) {
return (
<div className="p-6">
<div className="rounded-lg border border-red-400/20 p-4">
<p className="text-sm text-red-400">
Failed to load source:{" "}
{error ? String(error) : "Not found"}
</p>
</div>
</div>
);
}
return (
<div className="flex h-full flex-col">
<TopBarPortal
left={
<>
<TopBarItem
id="back"
label="Back"
priority="high"
>
<CircleButton
icon={ArrowLeft}
onClick={() => navigate("/sources")}
/>
</TopBarItem>
<TopBarItem
id="source-path"
label="Path"
priority="high"
>
<SourcePathBar
sourceName={source.name}
itemCount={source.item_count}
/>
</TopBarItem>
</>
}
right={
<>
<TopBarItem
id="search"
label="Search"
priority="high"
>
<ExpandableSearchButton
placeholder="Search items..."
value={searchValue}
onChange={setSearchValue}
onClear={() => setSearchValue("")}
/>
</TopBarItem>
<TopBarItem
id="sync"
label="Sync"
priority="high"
>
<CircleButton
icon={ArrowsClockwise}
onClick={() =>
syncMutation.mutate({
source_id: source.id,
})
}
title="Sync"
active={
syncMutation.isPending ||
source.status === "syncing"
}
/>
</TopBarItem>
<TopBarItem
id="more-actions"
label="More"
priority="normal"
>
<MoreActionsMenu
adapterHasUpdate={adapterHasUpdate}
onUpdate={() =>
updateMutation.mutate({
adapter_id: source.adapter_id,
})
}
isUpdating={updateMutation.isPending}
onDelete={() => setShowDeleteConfirm(true)}
/>
</TopBarItem>
</>
}
/>
{/* Banners */}
{(updateMutation.data ||
updateMutation.error ||
syncMutation.error ||
(syncMutation.data && !syncMutation.data.error)) && (
<div className="border-app-line/30 space-y-2 border-b px-6 py-3">
{updateMutation.data && (
<div className="border-accent/20 rounded-lg border p-3">
<p className="text-accent text-xs">
Updated {updateMutation.data.adapter_id}: v
{updateMutation.data.old_version} &rarr; v
{updateMutation.data.new_version}
{updateMutation.data.schema_changed
? " (schema changed — will migrate on next sync)"
: ""}
</p>
</div>
)}
{updateMutation.error && (
<div className="rounded-lg border border-red-400/20 p-3">
<p className="text-xs text-red-400">
Update failed: {String(updateMutation.error)}
</p>
</div>
)}
{syncMutation.error && (
<div className="rounded-lg border border-red-400/20 p-3">
<p className="text-xs text-red-400">
Sync failed: {String(syncMutation.error)}
</p>
</div>
)}
{syncMutation.data && !syncMutation.data.error && (
<div className="border-accent/20 rounded-lg border p-3">
<p className="text-accent text-xs">
Synced{" "}
{syncMutation.data.records_upserted} records in{" "}
{(
syncMutation.data.duration_ms / 1000
).toFixed(1)}
s
</p>
</div>
)}
</div>
)}
{/* Virtualized items list */}
<div ref={scrollRef} className="flex-1 overflow-y-auto">
{itemsLoading && (
<div className="text-ink-faint py-12 text-center text-sm">
Loading items...
</div>
)}
{!itemsLoading && filteredItems.length === 0 && (
<div className="text-ink-faint py-12 text-center text-sm">
{searchValue
? "No matching items"
: "No items yet. Run a sync to populate."}
</div>
)}
{filteredItems.length > 0 && (
<div
style={{
height: virtualizer.getTotalSize(),
position: "relative",
}}
>
{virtualizer.getVirtualItems().map((virtualRow) => {
const item = filteredItems[virtualRow.index];
if (!item) return null;
return (
<div
key={item.id}
style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: virtualRow.size,
transform: `translateY(${virtualRow.start}px)`,
}}
>
<SourceDataRow
title={item.title}
preview={item.preview}
subtitle={item.subtitle}
/>
</div>
);
})}
</div>
)}
{loadingMore && (
<div className="text-ink-faint py-4 text-center text-xs">
Loading more...
</div>
)}
</div>
{/* Delete confirmation */}
{showDeleteConfirm && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/40 backdrop-blur-sm">
<div className="border-app-line bg-app-box w-full max-w-sm rounded-xl border p-6 shadow-2xl">
<h3 className="text-ink text-base font-semibold">
Delete source
</h3>
<p className="text-ink-faint mt-2 text-sm">
This will permanently delete{" "}
<strong className="text-ink-dull">
{source.name}
</strong>{" "}
and all its data.
</p>
{deleteMutation.error && (
<p className="mt-2 text-xs text-red-400">
{String(deleteMutation.error)}
</p>
)}
<div className="mt-5 flex justify-end gap-2">
<button
onClick={() => setShowDeleteConfirm(false)}
disabled={deleteMutation.isPending}
className="border-app-line text-ink-faint hover:text-ink rounded-lg border px-3.5 py-1.5 text-sm font-medium transition-colors"
>
Cancel
</button>
<button
onClick={() =>
deleteMutation.mutate(
{ source_id: source.id },
{
onSuccess: () =>
navigate("/sources"),
},
)
}
disabled={deleteMutation.isPending}
className="rounded-lg bg-red-500 px-3.5 py-1.5 text-sm font-medium text-white transition-colors hover:bg-red-500/80 disabled:opacity-50"
>
{deleteMutation.isPending
? "Deleting..."
: "Delete"}
</button>
</div>
</div>
</div>
)}
</div>
);
}
function MoreActionsMenu({
adapterHasUpdate,
onUpdate,
isUpdating,
onDelete,
}: {
adapterHasUpdate: boolean;
onUpdate: () => void;
isUpdating: boolean;
onDelete: () => void;
}) {
const popover = usePopover();
return (
<Popover.Root open={popover.open} onOpenChange={popover.setOpen}>
<Popover.Trigger asChild>
<CircleButton icon={DotsThree} title="More actions" />
</Popover.Trigger>
<Popover.Content
side="bottom"
align="end"
sideOffset={8}
className="!bg-app-box z-50 w-[200px] !rounded-lg !p-1"
>
{adapterHasUpdate && (
<button
onClick={() => {
onUpdate();
popover.setOpen(false);
}}
disabled={isUpdating}
className="hover:bg-app-hover text-ink flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm disabled:opacity-40"
>
<ArrowSquareUp size={16} className="text-blue-400" />
{isUpdating ? "Updating..." : "Update adapter"}
</button>
)}
<button
onClick={() => {
onDelete();
popover.setOpen(false);
}}
className="hover:bg-app-hover flex w-full items-center gap-2 rounded-md px-2.5 py-1.5 text-left text-sm text-red-400"
>
<Trash size={16} />
Delete source
</button>
</Popover.Content>
</Popover.Root>
);
}

View File

@@ -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 (
<>
<TopBarPortal
left={
<>
<TopBarItem id="back" label="Back" priority="high">
<CircleButton
icon={ArrowLeft}
onClick={() => navigate(-1)}
/>
</TopBarItem>
<TopBarItem id="title" label="Title" priority="high">
<h1 className="text-ink text-xl font-bold">
Sources
</h1>
</TopBarItem>
</>
}
right={
<>
<TopBarItem id="search" label="Search" priority="high">
<SearchBar
placeholder="Search sources..."
value=""
onChange={() => {}}
onClear={() => {}}
className="w-64"
/>
</TopBarItem>
<TopBarItem id="add-source" label="Add Source" priority="high">
<CircleButton
icon={Plus}
onClick={() => createTab("Adapters", "/sources/adapters")}
title="Add Source"
/>
</TopBarItem>
</>
}
/>
<div className="p-6">
{isLoading && (
<div className="flex items-center justify-center py-20">
<div className="text-ink-faint text-sm">Loading...</div>
</div>
)}
{error && (
<div className="border-red-400/20 rounded-lg border p-4">
<p className="text-sm text-red-400">
Failed to load sources: {String(error)}
</p>
</div>
)}
{sources && sources.length === 0 && (
<div className="flex flex-col items-center justify-center py-20">
<p className="text-ink-dull text-sm">No sources yet</p>
<p className="text-ink-faint mt-1 text-xs">
Add a data source to get started
</p>
<button
onClick={() => createTab("Adapters", "/sources/adapters")}
className="bg-accent hover:bg-accent-deep mt-4 rounded-lg px-3.5 py-1.5 text-sm font-medium text-white transition-colors"
>
Add Source
</button>
</div>
)}
{sources && sources.length > 0 && (
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{sources.map((source) => (
<SourceCard key={source.id} source={source} />
))}
</div>
)}
</div>
</>
);
}

View File

@@ -14,6 +14,10 @@ export type ActiveJobsInput = Record<string, never>;
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<string, never>;
export type ListEventsInput = Record<string, never>;
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<string, never>;
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',