diff --git a/apps/playground/Gemfile.lock b/apps/playground/Gemfile.lock
new file mode 100644
index 00000000..c5e8a3d3
--- /dev/null
+++ b/apps/playground/Gemfile.lock
@@ -0,0 +1,137 @@
+GEM
+ remote: https://rubygems.org/
+ specs:
+ CFPropertyList (3.0.8)
+ activesupport (7.2.3)
+ base64
+ benchmark (>= 0.3)
+ bigdecimal
+ concurrent-ruby (~> 1.0, >= 1.3.1)
+ connection_pool (>= 2.2.5)
+ drb
+ i18n (>= 1.6, < 2)
+ logger (>= 1.4.2)
+ minitest (>= 5.1)
+ securerandom (>= 0.3)
+ tzinfo (~> 2.0, >= 2.0.5)
+ addressable (2.8.8)
+ public_suffix (>= 2.0.2, < 8.0)
+ algoliasearch (1.27.5)
+ httpclient (~> 2.8, >= 2.8.3)
+ json (>= 1.5.1)
+ atomos (0.1.3)
+ base64 (0.3.0)
+ benchmark (0.5.0)
+ bigdecimal (4.0.1)
+ claide (1.1.0)
+ cocoapods (1.15.2)
+ addressable (~> 2.8)
+ claide (>= 1.0.2, < 2.0)
+ cocoapods-core (= 1.15.2)
+ cocoapods-deintegrate (>= 1.0.3, < 2.0)
+ cocoapods-downloader (>= 2.1, < 3.0)
+ cocoapods-plugins (>= 1.0.0, < 2.0)
+ cocoapods-search (>= 1.0.0, < 2.0)
+ cocoapods-trunk (>= 1.6.0, < 2.0)
+ cocoapods-try (>= 1.1.0, < 2.0)
+ colored2 (~> 3.1)
+ escape (~> 0.0.4)
+ fourflusher (>= 2.3.0, < 3.0)
+ gh_inspector (~> 1.0)
+ molinillo (~> 0.8.0)
+ nap (~> 1.0)
+ ruby-macho (>= 2.3.0, < 3.0)
+ xcodeproj (>= 1.23.0, < 2.0)
+ cocoapods-core (1.15.2)
+ activesupport (>= 5.0, < 8)
+ addressable (~> 2.8)
+ algoliasearch (~> 1.0)
+ concurrent-ruby (~> 1.1)
+ fuzzy_match (~> 2.0.4)
+ nap (~> 1.0)
+ netrc (~> 0.11)
+ public_suffix (~> 4.0)
+ typhoeus (~> 1.0)
+ cocoapods-deintegrate (1.0.5)
+ cocoapods-downloader (2.1)
+ cocoapods-plugins (1.0.0)
+ nap
+ cocoapods-search (1.0.1)
+ cocoapods-trunk (1.6.0)
+ nap (>= 0.8, < 2.0)
+ netrc (~> 0.11)
+ cocoapods-try (1.2.0)
+ colored2 (3.1.2)
+ concurrent-ruby (1.3.6)
+ connection_pool (3.0.2)
+ drb (2.2.3)
+ escape (0.0.4)
+ ethon (0.15.0)
+ ffi (>= 1.15.0)
+ ffi (1.17.3)
+ ffi (1.17.3-aarch64-linux-gnu)
+ ffi (1.17.3-aarch64-linux-musl)
+ ffi (1.17.3-arm-linux-gnu)
+ ffi (1.17.3-arm-linux-musl)
+ ffi (1.17.3-arm64-darwin)
+ ffi (1.17.3-x86-linux-gnu)
+ ffi (1.17.3-x86-linux-musl)
+ ffi (1.17.3-x86_64-darwin)
+ ffi (1.17.3-x86_64-linux-gnu)
+ ffi (1.17.3-x86_64-linux-musl)
+ fourflusher (2.3.1)
+ fuzzy_match (2.0.4)
+ gh_inspector (1.1.3)
+ httpclient (2.9.0)
+ mutex_m
+ i18n (1.14.8)
+ concurrent-ruby (~> 1.0)
+ json (2.18.1)
+ logger (1.7.0)
+ minitest (6.0.1)
+ prism (~> 1.5)
+ molinillo (0.8.0)
+ mutex_m (0.3.0)
+ nanaimo (0.3.0)
+ nap (1.1.0)
+ netrc (0.11.0)
+ prism (1.9.0)
+ public_suffix (4.0.7)
+ rexml (3.4.4)
+ ruby-macho (2.5.1)
+ securerandom (0.4.1)
+ typhoeus (1.5.0)
+ ethon (>= 0.9.0, < 0.16.0)
+ tzinfo (2.0.6)
+ concurrent-ruby (~> 1.0)
+ xcodeproj (1.25.1)
+ CFPropertyList (>= 2.3.3, < 4.0)
+ atomos (~> 0.1.3)
+ claide (>= 1.0.2, < 2.0)
+ colored2 (~> 3.1)
+ nanaimo (~> 0.3.0)
+ rexml (>= 3.3.6, < 4.0)
+
+PLATFORMS
+ aarch64-linux-gnu
+ aarch64-linux-musl
+ arm-linux-gnu
+ arm-linux-musl
+ arm64-darwin
+ ruby
+ x86-linux-gnu
+ x86-linux-musl
+ x86_64-darwin
+ x86_64-linux-gnu
+ x86_64-linux-musl
+
+DEPENDENCIES
+ activesupport (>= 6.1.7.5, != 7.1.0)
+ cocoapods (>= 1.13, != 1.15.1, != 1.15.0)
+ xcodeproj (< 1.26.0)
+
+RUBY VERSION
+ ruby 3.3.4p94
+
+BUNDLED WITH
+ 2.6.8
diff --git a/apps/playground/ios/Playground.xcodeproj/project.pbxproj b/apps/playground/ios/Playground.xcodeproj/project.pbxproj
index 84ee3b30..c5791b07 100644
--- a/apps/playground/ios/Playground.xcodeproj/project.pbxproj
+++ b/apps/playground/ios/Playground.xcodeproj/project.pbxproj
@@ -470,6 +470,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 6B3J8SDXMN;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Playground/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
@@ -497,6 +498,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
+ DEVELOPMENT_TEAM = 6B3J8SDXMN;
INFOPLIST_FILE = Playground/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
diff --git a/apps/playground/ios/Playground/PrivacyInfo.xcprivacy b/apps/playground/ios/Playground/PrivacyInfo.xcprivacy
index 41b8317f..41da7af9 100644
--- a/apps/playground/ios/Playground/PrivacyInfo.xcprivacy
+++ b/apps/playground/ios/Playground/PrivacyInfo.xcprivacy
@@ -10,6 +10,7 @@
NSPrivacyAccessedAPITypeReasons
C617.1
+ 0A2A.1
@@ -20,6 +21,15 @@
CA92.1
+
+ NSPrivacyAccessedAPIType
+ NSPrivacyAccessedAPICategoryDiskSpace
+ NSPrivacyAccessedAPITypeReasons
+
+ 85F4.1
+ E174.1
+
+
NSPrivacyAccessedAPIType
NSPrivacyAccessedAPICategorySystemBootTime
diff --git a/apps/playground/ios/Podfile.lock b/apps/playground/ios/Podfile.lock
index 2fbdefbd..b5947efd 100644
--- a/apps/playground/ios/Podfile.lock
+++ b/apps/playground/ios/Podfile.lock
@@ -1619,6 +1619,27 @@ PODS:
- React-logger (= 0.76.0)
- React-perflogger (= 0.76.0)
- React-utils (= 0.76.0)
+ - ReactNativeFs (2.36.2):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.01.01.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
- RNScreens (4.4.0):
- DoubleConversion
- glog
@@ -1664,6 +1685,49 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
+ - RNSVG (15.8.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.01.01.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - RNSVG/common (= 15.8.0)
+ - Yoga
+ - RNSVG/common (15.8.0):
+ - DoubleConversion
+ - glog
+ - hermes-engine
+ - RCT-Folly (= 2024.01.01.00)
+ - RCTRequired
+ - RCTTypeSafety
+ - React-Core
+ - React-debug
+ - React-Fabric
+ - React-featureflags
+ - React-graphics
+ - React-ImageManager
+ - React-NativeModulesApple
+ - React-RCTFabric
+ - React-rendererdebug
+ - React-utils
+ - ReactCodegen
+ - ReactCommon/turbomodule/bridging
+ - ReactCommon/turbomodule/core
+ - Yoga
- SocketRocket (0.7.1)
- Yoga (0.0.0)
@@ -1736,7 +1800,9 @@ DEPENDENCIES:
- React-utils (from `../node_modules/react-native/ReactCommon/react/utils`)
- ReactCodegen (from `build/generated/ios`)
- ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
+ - "ReactNativeFs (from `../node_modules/@dr.pogodin/react-native-fs`)"
- RNScreens (from `../node_modules/react-native-screens`)
+ - RNSVG (from `../node_modules/react-native-svg`)
- Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
SPEC REPOS:
@@ -1877,8 +1943,12 @@ EXTERNAL SOURCES:
:path: build/generated/ios
ReactCommon:
:path: "../node_modules/react-native/ReactCommon"
+ ReactNativeFs:
+ :path: "../node_modules/@dr.pogodin/react-native-fs"
RNScreens:
:path: "../node_modules/react-native-screens"
+ RNSVG:
+ :path: "../node_modules/react-native-svg"
Yoga:
:path: "../node_modules/react-native/ReactCommon/yoga"
@@ -1949,10 +2019,12 @@ SPEC CHECKSUMS:
React-utils: e74516d5b9483c5530ec61e249e28b88729321d2
ReactCodegen: ff7512e124e3dc1363a4930a209d033354d2042a
ReactCommon: cde69a75746e8d7131f61c27155ee9dc42117003
+ ReactNativeFs: 0a5ad47d8f6086b07ffde582e75d902afebc57c9
RNScreens: 351f431ef2a042a1887d4d90e1c1024b8ae9d123
+ RNSVG: 030717ff82ea8f2117347c2fcf52a2d1eafba9ba
SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748
Yoga: f8ec45ce98bba1bc93dd28f2ee37215180e6d2b6
PODFILE CHECKSUM: 707ebfe14907d38253c9e6bcd81c22e8e8a158f2
-COCOAPODS: 1.16.2
+COCOAPODS: 1.15.2
diff --git a/apps/playground/package.json b/apps/playground/package.json
index 17714970..f4defa10 100644
--- a/apps/playground/package.json
+++ b/apps/playground/package.json
@@ -5,12 +5,14 @@
"start": "react-native start"
},
"dependencies": {
+ "@dr.pogodin/react-native-fs": "^2.36.2",
"@react-navigation/bottom-tabs": "^7.4.7",
"@react-navigation/elements": "^2.5.2",
"@react-navigation/native": "^7.1.14",
"@react-navigation/native-stack": "^7.3.21",
"@reduxjs/toolkit": "^2.8.2",
"@rozenite/expo-atlas-plugin": "workspace:*",
+ "@rozenite/file-system-plugin": "workspace:*",
"@rozenite/mmkv-plugin": "workspace:*",
"@rozenite/network-activity-plugin": "workspace:*",
"@rozenite/overlay-plugin": "workspace:*",
diff --git a/apps/playground/src/app/App.tsx b/apps/playground/src/app/App.tsx
index ce65c9a0..980c580f 100644
--- a/apps/playground/src/app/App.tsx
+++ b/apps/playground/src/app/App.tsx
@@ -25,10 +25,13 @@ import { PerformanceMonitorScreen } from './screens/PerformanceMonitorScreen';
import { ReduxTestScreen } from './screens/ReduxTestScreen';
import { RequestBodyTestScreen } from './screens/RequestBodyTestScreen';
import { RequireProfilerTestScreen } from './screens/RequireProfilerTestScreen';
+import { FileSystemTestScreen } from './screens/FileSystemTestScreen';
import { store } from './store';
import { useRequireProfilerDevTools } from '@rozenite/require-profiler-plugin';
import { withOnBootNetworkActivityRecording } from '@rozenite/network-activity-plugin';
import { RozeniteOverlay } from '@rozenite/overlay-plugin';
+import { useFileSystemDevTools } from '@rozenite/file-system-plugin';
+import * as RNFS from '@dr.pogodin/react-native-fs';
withOnBootNetworkActivityRecording();
@@ -48,6 +51,7 @@ const Wrapper = () => {
});
usePerformanceMonitorDevTools();
useRequireProfilerDevTools();
+ useFileSystemDevTools({ rnfs: RNFS });
return (
{
name="RequireProfilerTest"
component={RequireProfilerTestScreen}
/>
+
{
+ const locations = getSaveLocations();
+ const [selectedLocation, setSelectedLocation] = useState(
+ locations[0]!,
+ );
+ const [savedFiles, setSavedFiles] = useState([]);
+ const [loading, setLoading] = useState(false);
+
+ const refreshFiles = useCallback(async () => {
+ try {
+ const dirPath = selectedLocation.path.endsWith('/')
+ ? selectedLocation.path.slice(0, -1)
+ : selectedLocation.path;
+ const items = await RNFS.readDir(dirPath);
+ const pngFiles = items
+ .filter((item: any) => item.isFile() && item.name.endsWith('.png'))
+ .map((item: any) => ({
+ name: item.name,
+ path: item.path,
+ size: item.size,
+ }));
+ setSavedFiles(pngFiles);
+ } catch (error) {
+ console.error('Error reading directory:', error);
+ setSavedFiles([]);
+ }
+ }, [selectedLocation]);
+
+ useEffect(() => {
+ refreshFiles();
+ }, [refreshFiles]);
+
+ const handleSave = useCallback(async () => {
+ setLoading(true);
+ try {
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
+ const fileName = `react-logo-${timestamp}.png`;
+ const dirPath = selectedLocation.path.endsWith('/')
+ ? selectedLocation.path.slice(0, -1)
+ : selectedLocation.path;
+ const destPath = `${dirPath}/${fileName}`;
+
+ // Resolve the bundled asset URI
+ const asset = Image.resolveAssetSource(reactLogoPng);
+ const uri = asset.uri;
+
+ if (uri.startsWith('http://') || uri.startsWith('https://')) {
+ // Dev mode: Metro serves assets over HTTP
+ const result = await RNFS.downloadFile({
+ fromUrl: uri,
+ toFile: destPath,
+ }).promise;
+ if (result.statusCode !== 200) {
+ throw new Error(`Download failed with status ${result.statusCode}`);
+ }
+ } else {
+ // Release mode: asset is a local file
+ const sourcePath = uri.startsWith('file://') ? uri.slice(7) : uri;
+ await RNFS.copyFile(sourcePath, destPath);
+ }
+
+ Alert.alert('Saved', `File saved as ${fileName}`);
+ await refreshFiles();
+ } catch (error) {
+ Alert.alert('Error', `Failed to save file: ${error}`);
+ } finally {
+ setLoading(false);
+ }
+ }, [selectedLocation, refreshFiles]);
+
+ const handleRemove = useCallback(
+ async (file: SavedFile) => {
+ Alert.alert(
+ 'Confirm Delete',
+ `Are you sure you want to delete "${file.name}"?`,
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Delete',
+ style: 'destructive',
+ onPress: async () => {
+ try {
+ await RNFS.unlink(file.path);
+ Alert.alert('Deleted', `${file.name} removed`);
+ await refreshFiles();
+ } catch (error) {
+ Alert.alert('Error', `Failed to delete file: ${error}`);
+ }
+ },
+ },
+ ],
+ );
+ },
+ [refreshFiles],
+ );
+
+ const handleRemoveAll = useCallback(async () => {
+ if (savedFiles.length === 0) return;
+
+ Alert.alert(
+ 'Confirm Delete All',
+ `Are you sure you want to delete all ${savedFiles.length} PNG files?`,
+ [
+ { text: 'Cancel', style: 'cancel' },
+ {
+ text: 'Delete All',
+ style: 'destructive',
+ onPress: async () => {
+ try {
+ await Promise.all(savedFiles.map((f) => RNFS.unlink(f.path)));
+ Alert.alert('Deleted', 'All PNG files removed');
+ await refreshFiles();
+ } catch (error) {
+ Alert.alert('Error', `Failed to delete files: ${error}`);
+ }
+ },
+ },
+ ],
+ );
+ }, [savedFiles, refreshFiles]);
+
+ const formatBytes = (bytes: number) => {
+ if (bytes < 1024) return `${bytes} B`;
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
+ };
+
+ const renderFile = ({ item }: { item: SavedFile }) => (
+
+
+
+ {item.name}
+
+ {formatBytes(item.size)}
+
+ handleRemove(item)}
+ >
+ Remove
+
+
+ );
+
+ return (
+
+
+ File System Plugin
+
+ Save and manage files across device directories
+
+
+
+ {/* Location selector */}
+
+ Save Location
+
+ {locations.map((loc) => (
+ setSelectedLocation(loc)}
+ >
+
+ {loc.label}
+
+
+ ))}
+
+
+
+ {/* Actions */}
+
+
+
+ {loading ? 'Saving...' : 'Save React Logo'}
+
+
+
+
+
+ Remove All
+
+
+
+
+ {/* File list */}
+
+
+
+ PNG Files ({savedFiles.length})
+
+
+ Refresh
+
+
+
+ {savedFiles.length === 0 ? (
+
+ No PNG files found
+
+ Tap "Save React Logo" to write a file to the selected directory
+
+
+ ) : (
+ item.path}
+ showsVerticalScrollIndicator={false}
+ contentContainerStyle={styles.listContainer}
+ ItemSeparatorComponent={() => }
+ />
+ )}
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#0a0a0a',
+ },
+ header: {
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingHorizontal: 40,
+ paddingTop: 60,
+ paddingBottom: 16,
+ },
+ title: {
+ fontSize: 36,
+ fontWeight: 'bold',
+ color: '#ffffff',
+ textAlign: 'center',
+ marginBottom: 12,
+ letterSpacing: 1,
+ },
+ subtitle: {
+ fontSize: 16,
+ color: '#a0a0a0',
+ textAlign: 'center',
+ lineHeight: 24,
+ },
+ locationSection: {
+ paddingHorizontal: 20,
+ marginBottom: 16,
+ },
+ sectionLabel: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: '#a0a0a0',
+ marginBottom: 10,
+ textTransform: 'uppercase',
+ letterSpacing: 0.5,
+ },
+ locationTabs: {
+ flexDirection: 'row',
+ flexWrap: 'wrap',
+ gap: 8,
+ },
+ locationTab: {
+ backgroundColor: '#1a1a1a',
+ paddingVertical: 10,
+ paddingHorizontal: 14,
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: '#333333',
+ alignItems: 'center',
+ },
+ selectedLocationTab: {
+ backgroundColor: '#8232FF',
+ borderColor: '#8232FF',
+ },
+ locationTabText: {
+ fontSize: 13,
+ fontWeight: '600',
+ color: '#ffffff',
+ },
+ selectedLocationTabText: {
+ color: '#ffffff',
+ },
+ actions: {
+ flexDirection: 'row',
+ paddingHorizontal: 20,
+ gap: 12,
+ marginBottom: 20,
+ },
+ actionBtn: {
+ flex: 1,
+ paddingVertical: 16,
+ paddingHorizontal: 20,
+ borderRadius: 12,
+ alignItems: 'center',
+ },
+ saveBtn: {
+ backgroundColor: '#8232FF',
+ shadowColor: '#8232FF',
+ shadowOffset: { width: 0, height: 4 },
+ shadowOpacity: 0.3,
+ shadowRadius: 8,
+ elevation: 8,
+ },
+ removeAllBtn: {
+ backgroundColor: '#ff6b6b',
+ },
+ disabledBtn: {
+ backgroundColor: '#333333',
+ shadowOpacity: 0,
+ elevation: 0,
+ },
+ actionBtnText: {
+ color: '#ffffff',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ disabledBtnText: {
+ color: '#666666',
+ },
+ listSection: {
+ flex: 1,
+ paddingHorizontal: 20,
+ },
+ listHeader: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ marginBottom: 16,
+ },
+ listTitle: {
+ fontSize: 18,
+ fontWeight: 'bold',
+ color: '#ffffff',
+ },
+ refreshBtn: {
+ backgroundColor: '#666666',
+ paddingHorizontal: 16,
+ paddingVertical: 8,
+ borderRadius: 6,
+ },
+ refreshBtnText: {
+ color: '#ffffff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ listContainer: {
+ paddingVertical: 4,
+ },
+ fileCard: {
+ backgroundColor: '#1a1a1a',
+ padding: 16,
+ borderRadius: 12,
+ borderWidth: 1,
+ borderColor: '#333333',
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ },
+ fileInfo: {
+ flex: 1,
+ marginRight: 12,
+ },
+ fileName: {
+ fontSize: 14,
+ fontWeight: '600',
+ color: '#ffffff',
+ fontFamily: 'monospace',
+ marginBottom: 4,
+ },
+ fileSize: {
+ fontSize: 12,
+ color: '#a0a0a0',
+ },
+ removeButton: {
+ backgroundColor: '#ff6b6b',
+ paddingVertical: 8,
+ paddingHorizontal: 16,
+ borderRadius: 6,
+ },
+ removeButtonText: {
+ color: '#ffffff',
+ fontSize: 14,
+ fontWeight: '600',
+ },
+ separator: {
+ height: 10,
+ },
+ emptyState: {
+ flex: 1,
+ justifyContent: 'center',
+ alignItems: 'center',
+ paddingHorizontal: 40,
+ },
+ emptyStateText: {
+ fontSize: 18,
+ color: '#a0a0a0',
+ textAlign: 'center',
+ marginBottom: 8,
+ },
+ emptyStateSubtext: {
+ fontSize: 14,
+ color: '#666666',
+ textAlign: 'center',
+ },
+});
diff --git a/apps/playground/src/app/screens/LandingScreen.tsx b/apps/playground/src/app/screens/LandingScreen.tsx
index 79cbb8d6..461a430f 100644
--- a/apps/playground/src/app/screens/LandingScreen.tsx
+++ b/apps/playground/src/app/screens/LandingScreen.tsx
@@ -4,17 +4,26 @@ import {
StyleSheet,
TouchableOpacity,
Dimensions,
+ ScrollView,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
const { width } = Dimensions.get('window');
export const LandingScreen = () => {
const navigation = useNavigation();
+ const insets = useSafeAreaInsets();
return (
-
+
Rozenite
@@ -60,6 +69,13 @@ export const LandingScreen = () => {
Require Profiler Test
+ navigation.navigate('FileSystemTest' as never)}
+ >
+ File System
+
+
navigation.navigate('BottomTabs' as never)}
@@ -80,9 +96,8 @@ export const LandingScreen = () => {
communication between DevTools and React Native
-
-
-
+
+
);
};
@@ -94,9 +109,11 @@ const styles = StyleSheet.create({
backgroundGradient: {
flex: 1,
backgroundColor: '#0a0a0a',
+ },
+ scrollContent: {
+ flexGrow: 1,
justifyContent: 'center',
alignItems: 'center',
- position: 'relative',
},
content: {
alignItems: 'center',
diff --git a/apps/playground/src/assets/react.png b/apps/playground/src/assets/react.png
new file mode 100644
index 00000000..b6b6c43a
Binary files /dev/null and b/apps/playground/src/assets/react.png differ
diff --git a/apps/playground/tsconfig.app.json b/apps/playground/tsconfig.app.json
index 79f5506f..20784a77 100644
--- a/apps/playground/tsconfig.app.json
+++ b/apps/playground/tsconfig.app.json
@@ -28,6 +28,9 @@
],
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.js", "src/**/*.jsx"],
"references": [
+ {
+ "path": "../../packages/file-system-plugin"
+ },
{
"path": "../../packages/overlay-plugin"
},
diff --git a/commitlint.config.js b/commitlint.config.js
index 5a56beb4..d9f8b6cf 100644
--- a/commitlint.config.js
+++ b/commitlint.config.js
@@ -24,6 +24,7 @@ export default {
'react-navigation-plugin',
'require-profiler-plugin',
'overlay-plugin',
+ 'file-system-plugin',
'',
],
],
diff --git a/nx.json b/nx.json
index c53750f3..af1f06f9 100644
--- a/nx.json
+++ b/nx.json
@@ -57,7 +57,8 @@
"packages/performance-monitor-plugin/**/*",
"packages/react-navigation-plugin/**/*",
"packages/require-profiler-plugin/**/*",
- "packages/overlay-plugin/**/*"
+ "packages/overlay-plugin/**/*",
+ "packages/file-system-plugin/**/*"
]
},
{
diff --git a/packages/file-system-plugin/package.json b/packages/file-system-plugin/package.json
new file mode 100644
index 00000000..76fe1ad5
--- /dev/null
+++ b/packages/file-system-plugin/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "@rozenite/file-system-plugin",
+ "version": "1.0.0",
+ "description": "File system plugin for Rozenite.",
+ "type": "module",
+ "main": "./dist/react-native.cjs",
+ "module": "./dist/react-native.js",
+ "types": "./dist/react-native.d.ts",
+ "scripts": {
+ "build": "rozenite build",
+ "dev": "rozenite dev"
+ },
+ "dependencies": {
+ "@rozenite/plugin-bridge": "workspace:*"
+ },
+ "devDependencies": {
+ "@rozenite/vite-plugin": "workspace:*",
+ "@types/react": "~18.3.12",
+ "autoprefixer": "^10.4.21",
+ "lucide-react": "^0.263.1",
+ "postcss": "^8.5.6",
+ "react": "18.3.1",
+ "react-dom": "18.3.0",
+ "react-native": "0.76.0",
+ "react-native-web": "0.21.0",
+ "rozenite": "workspace:*",
+ "tailwindcss": "^3.4.17",
+ "tailwindcss-animate": "^1.0.7",
+ "typescript": "^5.7.3",
+ "vite": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": "*",
+ "react-native": "*"
+ },
+ "license": "MIT"
+}
diff --git a/packages/file-system-plugin/postcss.config.js b/packages/file-system-plugin/postcss.config.js
new file mode 100644
index 00000000..2aa7205d
--- /dev/null
+++ b/packages/file-system-plugin/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+};
diff --git a/packages/file-system-plugin/react-native.ts b/packages/file-system-plugin/react-native.ts
new file mode 100644
index 00000000..e55b933b
--- /dev/null
+++ b/packages/file-system-plugin/react-native.ts
@@ -0,0 +1,510 @@
+import { useEffect, useRef } from 'react';
+import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
+import type {
+ FileSystemEventMap,
+ FileSystemProvider,
+ FsEntry,
+} from './src/shared/protocol';
+import { PLUGIN_ID } from './src/shared/protocol';
+import {
+ joinPath,
+ mimeTypeFromName,
+ normalizeDirPath,
+} from './src/shared/path';
+
+type ProviderImpl = {
+ provider: Exclude;
+ getRoots: () => Promise<
+ Array<{
+ id: string;
+ label: string;
+ path: string;
+ }>
+ >;
+ listDir: (path: string) => Promise;
+ readImageBase64: (
+ path: string,
+ maxBytes: number,
+ ) => Promise<{ mime: string; base64: string }>;
+ readTextFile: (path: string, maxBytes: number) => Promise;
+};
+
+export type UseFileSystemDevToolsOptions = {
+ /**
+ * Pass Expo FileSystem module from the host app.
+ */
+ expoFileSystem?: any;
+ /**
+ * Pass RNFS module from the host app.
+ * Supports `react-native-fs` or `@dr.pogodin/react-native-fs` (same surface).
+ */
+ rnfs?: any;
+};
+
+async function detectProvider(
+ options?: UseFileSystemDevToolsOptions,
+): Promise {
+ if (!options?.expoFileSystem && !options?.rnfs) return null;
+
+ if (options?.expoFileSystem) {
+ const FileSystem =
+ options.expoFileSystem?.default ?? options.expoFileSystem;
+ const expo: ProviderImpl = {
+ provider: 'expo',
+ async getRoots() {
+ const roots: Array<{ id: string; label: string; path: string }> = [];
+ if (FileSystem.documentDirectory) {
+ roots.push({
+ id: 'expo.documentDirectory',
+ label: 'Document Directory',
+ path: normalizeDirPath(FileSystem.documentDirectory),
+ });
+ }
+ if (FileSystem.cacheDirectory) {
+ roots.push({
+ id: 'expo.cacheDirectory',
+ label: 'Cache Directory',
+ path: normalizeDirPath(FileSystem.cacheDirectory),
+ });
+ }
+ if (FileSystem.bundleDirectory) {
+ roots.push({
+ id: 'expo.bundleDirectory',
+ label: 'Bundle Directory',
+ path: normalizeDirPath(FileSystem.bundleDirectory),
+ });
+ }
+ return roots;
+ },
+ async listDir(path) {
+ const dir = normalizeDirPath(path);
+ const rawItems: string[] = await FileSystem.readDirectoryAsync(dir);
+
+ // Cache directories can be huge; avoid timeouts by limiting work.
+ const MAX_ENTRIES = 400;
+ const CONCURRENCY = 12;
+ const limited = rawItems.slice(0, MAX_ENTRIES);
+
+ const entries = await mapWithConcurrency(
+ limited,
+ CONCURRENCY,
+ async (raw: string) => {
+ const child = resolveExpoChildPath(dir, raw);
+ const info = await FileSystem.getInfoAsync(child, { size: true });
+ const isDirectory = Boolean(info.isDirectory);
+ return {
+ name: basename(raw),
+ path: isDirectory ? normalizeDirPath(child) : child,
+ isDirectory,
+ size: info.size ?? null,
+ modifiedAtMs:
+ typeof info.modificationTime === 'number'
+ ? info.modificationTime * 1000
+ : null,
+ mimeTypeHint: mimeTypeFromName(child),
+ } satisfies FsEntry;
+ },
+ );
+
+ if (rawItems.length > MAX_ENTRIES) {
+ entries.push({
+ name: `… (${rawItems.length - MAX_ENTRIES} more not shown)`,
+ path: joinPath(dir, `__ROZENITE_TRUNCATED__${Date.now()}`),
+ isDirectory: false,
+ size: null,
+ modifiedAtMs: null,
+ mimeTypeHint: null,
+ });
+ }
+ entries.sort((a, b) => {
+ if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
+ return a.name.localeCompare(b.name);
+ });
+ return entries;
+ },
+ async readImageBase64(path, maxBytes) {
+ const info = await FileSystem.getInfoAsync(path, { size: true });
+ const size = info.size ?? 0;
+ if (maxBytes > 0 && size > maxBytes) {
+ throw new Error(
+ `File is too large for preview (${size} bytes, limit ${maxBytes})`,
+ );
+ }
+ const base64 = await FileSystem.readAsStringAsync(path, {
+ encoding: 'base64',
+ });
+ const mime = mimeTypeFromName(path) ?? 'application/octet-stream';
+ return { mime, base64 };
+ },
+ async readTextFile(path, maxBytes) {
+ const info = await FileSystem.getInfoAsync(path, { size: true });
+ const size = info.size ?? 0;
+ if (maxBytes > 0 && size > maxBytes) {
+ throw new Error(
+ `File is too large for preview (${size} bytes, limit ${maxBytes})`,
+ );
+ }
+ // Try UTF-8 first, fall back to base64 for binary files
+ try {
+ return await FileSystem.readAsStringAsync(path, { encoding: 'utf8' });
+ } catch {
+ // File might be binary, try base64
+ const base64 = await FileSystem.readAsStringAsync(path, {
+ encoding: 'base64',
+ });
+ // Decode base64 to show raw bytes as hex
+ return (
+ `[Binary file - ${size} bytes]\n\n` + formatBase64AsHex(base64)
+ );
+ }
+ },
+ };
+ return expo;
+ }
+
+ const RNFS = options.rnfs;
+ const rnfs: ProviderImpl = {
+ provider: 'rnfs',
+ async getRoots() {
+ const roots: Array<{ id: string; label: string; path: string }> = [];
+ roots.push({
+ id: 'rnfs.DocumentDirectoryPath',
+ label: 'Document Directory',
+ path: normalizeDirPath(RNFS.DocumentDirectoryPath),
+ });
+ roots.push({
+ id: 'rnfs.CachesDirectoryPath',
+ label: 'Caches Directory',
+ path: normalizeDirPath(RNFS.CachesDirectoryPath),
+ });
+ roots.push({
+ id: 'rnfs.TemporaryDirectoryPath',
+ label: 'Temporary Directory',
+ path: normalizeDirPath(RNFS.TemporaryDirectoryPath),
+ });
+ roots.push({
+ id: 'rnfs.LibraryDirectoryPath',
+ label: 'Library Directory',
+ path: normalizeDirPath(RNFS.LibraryDirectoryPath),
+ });
+ if (RNFS.MainBundlePath) {
+ roots.push({
+ id: 'rnfs.MainBundlePath',
+ label: 'Main Bundle',
+ path: normalizeDirPath(RNFS.MainBundlePath),
+ });
+ }
+ return roots;
+ },
+ async listDir(path) {
+ const normalized = normalizeDirPath(path);
+ const dir =
+ normalized.length > 1 && normalized.endsWith('/')
+ ? normalized.slice(0, -1)
+ : normalized;
+ const items = await RNFS.readDir(dir);
+ const entries: FsEntry[] = items.map((it: any) => {
+ const isDirectory = it.isDirectory();
+ return {
+ name: it.name,
+ path: isDirectory ? normalizeDirPath(it.path) : it.path,
+ isDirectory,
+ size: it.isFile() ? it.size : null,
+ modifiedAtMs: it.mtime ? it.mtime.getTime() : null,
+ mimeTypeHint: mimeTypeFromName(it.path),
+ };
+ });
+ entries.sort((a, b) => {
+ if (a.isDirectory !== b.isDirectory) return a.isDirectory ? -1 : 1;
+ return a.name.localeCompare(b.name);
+ });
+ return entries;
+ },
+ async readImageBase64(path, maxBytes) {
+ const st = await RNFS.stat(path);
+ const size = st.size ?? 0;
+ if (maxBytes > 0 && size > maxBytes) {
+ throw new Error(
+ `File is too large for preview (${size} bytes, limit ${maxBytes})`,
+ );
+ }
+ const base64 = await RNFS.readFile(path, 'base64');
+ const mime = mimeTypeFromName(path) ?? 'application/octet-stream';
+ return { mime, base64 };
+ },
+ async readTextFile(path, maxBytes) {
+ const st = await RNFS.stat(path);
+ const size = st.size ?? 0;
+ if (maxBytes > 0 && size > maxBytes) {
+ throw new Error(
+ `File is too large for preview (${size} bytes, limit ${maxBytes})`,
+ );
+ }
+ // Try UTF-8 first, fall back to base64 for binary files
+ try {
+ return await RNFS.readFile(path, 'utf8');
+ } catch {
+ // File might be binary, try base64
+ const base64 = await RNFS.readFile(path, 'base64');
+ return `[Binary file - ${size} bytes]\n\n` + formatBase64AsHex(base64);
+ }
+ },
+ };
+ return rnfs;
+}
+
+function resolveExpoChildPath(dirUri: string, item: string): string {
+ // Legacy FS can return either leaf names or full URIs/paths.
+ if (!item) return dirUri;
+ if (item.startsWith('file://')) return item;
+ if (item.startsWith('/')) return item;
+ if (item.startsWith(dirUri)) return item;
+ return joinPath(dirUri, item);
+}
+
+function basename(pathOrUri: string): string {
+ if (!pathOrUri) return '';
+ // Strip query/hash for safety
+ const clean = pathOrUri.split('?')[0].split('#')[0];
+ const noTrailing = clean.endsWith('/') ? clean.slice(0, -1) : clean;
+ const idx = noTrailing.lastIndexOf('/');
+ return idx >= 0 ? noTrailing.slice(idx + 1) : noTrailing;
+}
+
+async function mapWithConcurrency(
+ items: TInput[],
+ concurrency: number,
+ worker: (item: TInput) => Promise,
+): Promise {
+ const results: TOutput[] = new Array(items.length);
+ let idx = 0;
+
+ const runners = new Array(Math.max(1, concurrency)).fill(0).map(async () => {
+ while (idx < items.length) {
+ const current = idx++;
+ results[current] = await worker(items[current]);
+ }
+ });
+
+ await Promise.all(runners);
+ return results;
+}
+
+function safeError(err: unknown): string {
+ if (err instanceof Error) return err.message;
+ try {
+ return JSON.stringify(err);
+ } catch {
+ return String(err);
+ }
+}
+
+function formatBase64AsHex(base64: string): string {
+ // Decode base64 to bytes and format as hex dump
+ const chars =
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+ const bytes: number[] = [];
+
+ // Remove padding and decode
+ const clean = base64.replace(/=/g, '');
+ for (let i = 0; i < clean.length; i += 4) {
+ const a = chars.indexOf(clean[i] || 'A');
+ const b = chars.indexOf(clean[i + 1] || 'A');
+ const c = chars.indexOf(clean[i + 2] || 'A');
+ const d = chars.indexOf(clean[i + 3] || 'A');
+
+ bytes.push((a << 2) | (b >> 4));
+ if (i + 2 < clean.length) bytes.push(((b & 15) << 4) | (c >> 2));
+ if (i + 3 < clean.length) bytes.push(((c & 3) << 6) | d);
+ }
+
+ // Limit to first 512 bytes for preview
+ const limited = bytes.slice(0, 512);
+ const lines: string[] = [];
+
+ for (let i = 0; i < limited.length; i += 16) {
+ const slice = limited.slice(i, i + 16);
+ const hex = slice.map((b) => b.toString(16).padStart(2, '0')).join(' ');
+ const ascii = slice
+ .map((b) => (b >= 32 && b < 127 ? String.fromCharCode(b) : '.'))
+ .join('');
+ const offset = i.toString(16).padStart(8, '0');
+ lines.push(`${offset} ${hex.padEnd(48)} ${ascii}`);
+ }
+
+ if (bytes.length > 512) {
+ lines.push(`\n... (${bytes.length - 512} more bytes not shown)`);
+ }
+
+ return lines.join('\n');
+}
+
+export const useFileSystemDevTools = (
+ options?: UseFileSystemDevToolsOptions,
+) => {
+ const client = useRozeniteDevToolsClient({
+ pluginId: PLUGIN_ID,
+ });
+
+ const subsRef = useRef void }>>([]);
+
+ useEffect(() => {
+ if (!client) return;
+
+ // Notify the panel that the RN side is ready (handles app reload case)
+ client.send('fs:ready', { timestamp: Date.now() });
+
+ subsRef.current.push(
+ client.onMessage('fs:get-roots', async ({ requestId }) => {
+ try {
+ const provider = await detectProvider(options);
+ if (!provider) {
+ client.send('fs:get-roots:result', {
+ requestId,
+ provider: 'none',
+ roots: [],
+ error:
+ 'No filesystem provider detected. Pass `{ expoFileSystem: FileSystem }` (Expo) or `{ rnfs: RNFS }` (bare RN) to `useFileSystemDevTools()`.',
+ });
+ return;
+ }
+ const roots = await provider.getRoots();
+ client.send('fs:get-roots:result', {
+ requestId,
+ provider: provider.provider,
+ roots,
+ });
+ } catch (e) {
+ client.send('fs:get-roots:result', {
+ requestId,
+ provider: 'none',
+ roots: [],
+ error: safeError(e),
+ });
+ }
+ }),
+ );
+
+ subsRef.current.push(
+ client.onMessage('fs:list', async ({ requestId, path }) => {
+ try {
+ const provider = await detectProvider(options);
+ if (!provider) {
+ client.send('fs:list:result', {
+ requestId,
+ provider: 'none',
+ path,
+ entries: [],
+ error:
+ 'No filesystem provider detected. Pass `{ expoFileSystem: FileSystem }` (Expo) or `{ rnfs: RNFS }` (bare RN) to `useFileSystemDevTools()`.',
+ });
+ return;
+ }
+ const entries = await provider.listDir(path);
+ client.send('fs:list:result', {
+ requestId,
+ provider: provider.provider,
+ path,
+ entries,
+ });
+ } catch (e) {
+ const provider = await detectProvider(options);
+ client.send('fs:list:result', {
+ requestId,
+ provider: provider?.provider ?? 'none',
+ path,
+ entries: [],
+ error: safeError(e),
+ });
+ }
+ }),
+ );
+
+ subsRef.current.push(
+ client.onMessage(
+ 'fs:read-image',
+ async ({ requestId, path, maxBytes }) => {
+ try {
+ const provider = await detectProvider(options);
+ if (!provider) {
+ client.send('fs:read-image:result', {
+ requestId,
+ provider: 'none',
+ path,
+ error:
+ 'No filesystem provider detected. Pass `{ expoFileSystem: FileSystem }` (Expo) or `{ rnfs: RNFS }` (bare RN) to `useFileSystemDevTools()`.',
+ });
+ return;
+ }
+
+ const { mime, base64 } = await provider.readImageBase64(
+ path,
+ typeof maxBytes === 'number' ? maxBytes : 10_000_000,
+ );
+
+ client.send('fs:read-image:result', {
+ requestId,
+ provider: provider.provider,
+ path,
+ dataUri: `data:${mime};base64,${base64}`,
+ });
+ } catch (e) {
+ const provider = await detectProvider(options);
+ client.send('fs:read-image:result', {
+ requestId,
+ provider: provider?.provider ?? 'none',
+ path,
+ error: safeError(e),
+ });
+ }
+ },
+ ),
+ );
+
+ subsRef.current.push(
+ client.onMessage(
+ 'fs:read-file',
+ async ({ requestId, path, maxBytes }) => {
+ try {
+ const provider = await detectProvider(options);
+ if (!provider) {
+ client.send('fs:read-file:result', {
+ requestId,
+ provider: 'none',
+ path,
+ error:
+ 'No filesystem provider detected. Pass `{ expoFileSystem: FileSystem }` (Expo) or `{ rnfs: RNFS }` (bare RN) to `useFileSystemDevTools()`.',
+ });
+ return;
+ }
+
+ const content = await provider.readTextFile(
+ path,
+ typeof maxBytes === 'number' ? maxBytes : 10_000_000,
+ );
+
+ client.send('fs:read-file:result', {
+ requestId,
+ provider: provider.provider,
+ path,
+ content,
+ });
+ } catch (e) {
+ const provider = await detectProvider(options);
+ client.send('fs:read-file:result', {
+ requestId,
+ provider: provider?.provider ?? 'none',
+ path,
+ error: safeError(e),
+ });
+ }
+ },
+ ),
+ );
+
+ return () => {
+ subsRef.current.forEach((s) => s.remove());
+ subsRef.current = [];
+ };
+ }, [client]);
+};
diff --git a/packages/file-system-plugin/rozenite.config.ts b/packages/file-system-plugin/rozenite.config.ts
new file mode 100644
index 00000000..ea0ccf9e
--- /dev/null
+++ b/packages/file-system-plugin/rozenite.config.ts
@@ -0,0 +1,8 @@
+export default {
+ panels: [
+ {
+ name: 'File System',
+ source: './src/file-system.tsx',
+ },
+ ],
+};
diff --git a/packages/file-system-plugin/src/file-system.tsx b/packages/file-system-plugin/src/file-system.tsx
new file mode 100644
index 00000000..da33721d
--- /dev/null
+++ b/packages/file-system-plugin/src/file-system.tsx
@@ -0,0 +1,165 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import {
+ ActivityIndicator,
+ FlatList,
+ SafeAreaView,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
+import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
+import type { FileSystemEventMap, FsEntry } from './shared/protocol';
+import { PLUGIN_ID } from './shared/protocol';
+import { useFileSystemRequests } from './use-file-system-requests';
+import { useFileSystemNavigation } from './use-file-system-navigation';
+import { ConnectingScreen } from './ui/ConnectingScreen';
+import { TopBar } from './ui/TopBar';
+import { FileEntryRow } from './ui/FileEntryRow';
+import { DetailPanel } from './ui/DetailPanel';
+import { PathDisplay } from './ui/PathDisplay';
+
+export default function FileSystemPanel() {
+ const client = useRozeniteDevToolsClient({
+ pluginId: PLUGIN_ID,
+ });
+
+ const requests = useFileSystemRequests(client);
+ const nav = useFileSystemNavigation(client, requests);
+
+ const [selected, setSelected] = useState(null);
+
+ // Clear selection when the directory changes (preserves original loadDir behavior)
+ useEffect(() => {
+ setSelected(null);
+ }, [nav.currentPath]);
+
+ const onSelectEntry = useCallback(
+ (entry: FsEntry) => {
+ if (entry.isDirectory) {
+ setSelected(null);
+ nav.setCurrentPath(entry.path);
+ return;
+ }
+ setSelected(entry);
+ },
+ [nav.setCurrentPath],
+ );
+
+ const renderItem = useCallback(
+ ({ item }: { item: FsEntry }) => (
+
+ ),
+ [selected?.path, onSelectEntry],
+ );
+
+ const keyExtractor = useCallback((item: FsEntry) => item.path, []);
+
+ if (!client) {
+ return ;
+ }
+
+ return (
+
+
+
+
+
+
+ {nav.currentPath ? (
+
+ ) : (
+ —
+ )}
+ {nav.loading ? : null}
+
+
+ {nav.error ? (
+
+ Error
+ {nav.error}
+
+ ) : null}
+
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#0b0b0f',
+ },
+ body: {
+ flex: 1,
+ flexDirection: 'row',
+ },
+ listPane: {
+ flex: 1.2,
+ borderRightWidth: 1,
+ borderRightColor: '#1c1c24',
+ },
+ listHeader: {
+ paddingHorizontal: 12,
+ paddingVertical: 8,
+ borderBottomWidth: 1,
+ borderBottomColor: '#1c1c24',
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 10,
+ },
+ listHeaderTitle: {
+ flex: 1,
+ color: '#eaeaf2',
+ fontSize: 12,
+ fontFamily: 'Menlo',
+ },
+ listContent: {
+ paddingVertical: 6,
+ },
+ errorBox: {
+ margin: 16,
+ padding: 12,
+ borderRadius: 10,
+ borderWidth: 1,
+ borderColor: 'rgba(255, 99, 132, 0.4)',
+ backgroundColor: 'rgba(255, 99, 132, 0.08)',
+ gap: 6,
+ },
+ errorTitle: {
+ color: '#ffb3c1',
+ fontWeight: '700',
+ },
+ errorText: {
+ color: '#ffb3c1',
+ fontSize: 12,
+ },
+});
diff --git a/packages/file-system-plugin/src/formatters.ts b/packages/file-system-plugin/src/formatters.ts
new file mode 100644
index 00000000..64c3ab9a
--- /dev/null
+++ b/packages/file-system-plugin/src/formatters.ts
@@ -0,0 +1,115 @@
+// ============================================================================
+// Content Formatters
+// Add new formatters here to support pretty-printing additional formats.
+// Each formatter returns { success: true, formatted: string } or { success: false }
+// ============================================================================
+
+type FormatResult = { success: true; formatted: string } | { success: false };
+
+type ContentFormatter = (content: string) => FormatResult;
+
+const formatters: ContentFormatter[] = [
+ // JSON formatter
+ (content: string): FormatResult => {
+ try {
+ const parsed = JSON.parse(content);
+ return { success: true, formatted: JSON.stringify(parsed, null, 2) };
+ } catch {
+ return { success: false };
+ }
+ },
+
+ // PLIST / XML formatter
+ (content: string): FormatResult => {
+ // Remove BOM if present and trim
+ const cleaned = content.replace(/^\uFEFF/, '').trim();
+
+ // Check if it looks like XML/PLIST (handle various XML starts)
+ const looksLikeXml =
+ cleaned.startsWith('\s+<') // Remove whitespace between tags
+ .replace(//g, '>\n') // Add newline after each closing tag
+ .split('\n')
+ .map((line) => line.trim())
+ .filter((line) => line.length > 0);
+
+ for (const token of normalized) {
+ // Handle closing tags - decrease indent first
+ if (token.startsWith('')) {
+ indent = Math.max(0, indent - 1);
+ formatted += tab.repeat(indent) + token + '\n';
+ continue;
+ }
+
+ // Handle self-closing tags and processing instructions
+ if (
+ token.endsWith('/>') ||
+ token.startsWith('') ||
+ token.startsWith('value
+ if (token.startsWith('<') && token.includes('')) {
+ formatted += tab.repeat(indent) + token + '\n';
+ continue;
+ }
+
+ // Handle opening tags - add then increase indent
+ if (token.startsWith('<')) {
+ formatted += tab.repeat(indent) + token + '\n';
+ indent++;
+ continue;
+ }
+
+ // Text content
+ formatted += tab.repeat(indent) + token + '\n';
+ }
+
+ return formatted.trim();
+}
+
+export function formatTextPreview(content: string): string {
+ // Try each formatter in order, return first successful result
+ for (const formatter of formatters) {
+ const result = formatter(content);
+ if (result.success) {
+ return result.formatted;
+ }
+ }
+ // No formatter matched, return raw content
+ return content;
+}
diff --git a/packages/file-system-plugin/src/shared/optional-modules.d.ts b/packages/file-system-plugin/src/shared/optional-modules.d.ts
new file mode 100644
index 00000000..c04e0a86
--- /dev/null
+++ b/packages/file-system-plugin/src/shared/optional-modules.d.ts
@@ -0,0 +1,59 @@
+declare module 'expo-file-system' {
+ export type FileInfo = {
+ exists: boolean;
+ isDirectory?: boolean;
+ size?: number;
+ modificationTime?: number; // seconds
+ uri: string;
+ };
+
+ export const cacheDirectory: string | null;
+ export const documentDirectory: string | null;
+ export const bundleDirectory: string | null;
+
+ export function readDirectoryAsync(uri: string): Promise;
+ export function getInfoAsync(
+ uri: string,
+ options?: { size?: boolean; md5?: boolean },
+ ): Promise;
+ export function readAsStringAsync(
+ uri: string,
+ options?: { encoding?: 'base64' | 'utf8' },
+ ): Promise;
+}
+
+declare module 'react-native-fs' {
+ export type ReadDirItem = {
+ name: string;
+ path: string;
+ size: number;
+ mtime?: Date;
+ isFile: () => boolean;
+ isDirectory: () => boolean;
+ };
+
+ export const MainBundlePath: string | undefined;
+ export const CachesDirectoryPath: string;
+ export const DocumentDirectoryPath: string;
+ export const TemporaryDirectoryPath: string;
+ export const LibraryDirectoryPath: string;
+
+ export function readDir(path: string): Promise;
+ export function stat(path: string): Promise<{
+ size: number;
+ mtime?: Date;
+ isFile: () => boolean;
+ isDirectory: () => boolean;
+ }>;
+ export function readFile(
+ path: string,
+ encoding: 'base64' | 'utf8',
+ ): Promise;
+}
+
+declare module '@birdofpreyru/react-native-fs' {
+ export * from 'react-native-fs';
+}
+declare module '@dr.pogodin/react-native-fs' {
+ export * from 'react-native-fs';
+}
diff --git a/packages/file-system-plugin/src/shared/path.ts b/packages/file-system-plugin/src/shared/path.ts
new file mode 100644
index 00000000..89581db7
--- /dev/null
+++ b/packages/file-system-plugin/src/shared/path.ts
@@ -0,0 +1,88 @@
+/**
+ * Path helpers that work with:
+ * - Bare RN paths: `/data/user/0/...`
+ * - Expo FileSystem URIs: `file:///data/user/0/...`
+ */
+
+export function normalizeDirPath(path: string): string {
+ if (!path) return path;
+ // Keep "file://" URIs intact; just ensure directories end with a trailing slash
+ const isFileUri = path.startsWith("file://");
+ if (isFileUri) {
+ return path.endsWith("/") ? path : `${path}/`;
+ }
+ return path.endsWith("/") ? path : `${path}/`;
+}
+
+export function joinPath(dir: string, name: string): string {
+ if (!dir) return name;
+ if (!name) return dir;
+ const d = dir.endsWith("/") ? dir : `${dir}/`;
+ return `${d}${name}`;
+}
+
+export function parentPath(path: string): string | null {
+ if (!path) return null;
+ const normalized = path.endsWith("/") ? path.slice(0, -1) : path;
+ const idx = normalized.lastIndexOf("/");
+ if (idx <= 0) {
+ // `file:///` or `/`
+ if (normalized.startsWith("file://")) return "file:///";
+ return "/";
+ }
+ return normalized.slice(0, idx + 1);
+}
+
+export function isLikelyImageFile(nameOrPath: string): boolean {
+ const lower = nameOrPath.toLowerCase();
+ return (
+ lower.endsWith(".png") ||
+ lower.endsWith(".jpg") ||
+ lower.endsWith(".jpeg") ||
+ lower.endsWith(".gif") ||
+ lower.endsWith(".webp") ||
+ lower.endsWith(".heic") ||
+ lower.endsWith(".heif")
+ );
+}
+
+export function isJsonFile(nameOrPath: string): boolean {
+ return nameOrPath.toLowerCase().endsWith(".json");
+}
+
+export function isLikelyTextFile(nameOrPath: string): boolean {
+ const lower = nameOrPath.toLowerCase();
+ return (
+ lower.endsWith(".txt") ||
+ lower.endsWith(".json") ||
+ lower.endsWith(".xml") ||
+ lower.endsWith(".log") ||
+ lower.endsWith(".md") ||
+ lower.endsWith(".yaml") ||
+ lower.endsWith(".yml") ||
+ lower.endsWith(".csv") ||
+ lower.endsWith(".html") ||
+ lower.endsWith(".css") ||
+ lower.endsWith(".js") ||
+ lower.endsWith(".ts") ||
+ lower.endsWith(".jsx") ||
+ lower.endsWith(".tsx") ||
+ lower.endsWith(".sh") ||
+ lower.endsWith(".env") ||
+ lower.endsWith(".gitignore") ||
+ lower.endsWith(".config") ||
+ lower.endsWith(".ini") ||
+ lower.endsWith(".plist")
+ );
+}
+
+export function mimeTypeFromName(nameOrPath: string): string | null {
+ const lower = nameOrPath.toLowerCase();
+ if (lower.endsWith(".png")) return "image/png";
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
+ if (lower.endsWith(".gif")) return "image/gif";
+ if (lower.endsWith(".webp")) return "image/webp";
+ if (lower.endsWith(".heic")) return "image/heic";
+ if (lower.endsWith(".heif")) return "image/heif";
+ return null;
+}
diff --git a/packages/file-system-plugin/src/shared/protocol.ts b/packages/file-system-plugin/src/shared/protocol.ts
new file mode 100644
index 00000000..1d57cbdd
--- /dev/null
+++ b/packages/file-system-plugin/src/shared/protocol.ts
@@ -0,0 +1,64 @@
+export const PLUGIN_ID = "file-system";
+
+export type FileSystemProvider = "expo" | "rnfs" | "none";
+
+export type FsEntry = {
+ name: string;
+ path: string;
+ isDirectory: boolean;
+ size?: number | null;
+ modifiedAtMs?: number | null;
+ mimeTypeHint?: string | null;
+};
+
+export type FsRoots = {
+ provider: FileSystemProvider;
+ roots: Array<{
+ id: string;
+ label: string;
+ path: string;
+ }>;
+};
+
+export type FileSystemEventMap = {
+ // Sent by RN side when it initializes/reconnects - panel should re-fetch data
+ "fs:ready": { timestamp: number };
+
+ "fs:get-roots": { requestId: string };
+ "fs:get-roots:result": { requestId: string } & FsRoots & { error?: string };
+
+ "fs:list": { requestId: string; path: string };
+ "fs:list:result": {
+ requestId: string;
+ provider: FileSystemProvider;
+ path: string;
+ entries: FsEntry[];
+ error?: string;
+ };
+
+ "fs:read-image": {
+ requestId: string;
+ path: string;
+ maxBytes?: number;
+ };
+ "fs:read-image:result": {
+ requestId: string;
+ provider: FileSystemProvider;
+ path: string;
+ dataUri?: string;
+ error?: string;
+ };
+
+ "fs:read-file": {
+ requestId: string;
+ path: string;
+ maxBytes?: number;
+ };
+ "fs:read-file:result": {
+ requestId: string;
+ provider: FileSystemProvider;
+ path: string;
+ content?: string;
+ error?: string;
+ };
+};
diff --git a/packages/file-system-plugin/src/types.ts b/packages/file-system-plugin/src/types.ts
new file mode 100644
index 00000000..5efee8e3
--- /dev/null
+++ b/packages/file-system-plugin/src/types.ts
@@ -0,0 +1,6 @@
+import type { PressableStateCallbackType } from 'react-native';
+
+// Extended pressable state for react-native-web (includes hover)
+export type WebPressableState = PressableStateCallbackType & {
+ hovered?: boolean;
+};
diff --git a/packages/file-system-plugin/src/ui/ConnectingScreen.tsx b/packages/file-system-plugin/src/ui/ConnectingScreen.tsx
new file mode 100644
index 00000000..0b09dcec
--- /dev/null
+++ b/packages/file-system-plugin/src/ui/ConnectingScreen.tsx
@@ -0,0 +1,36 @@
+import React from 'react';
+import {
+ ActivityIndicator,
+ SafeAreaView,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
+
+export function ConnectingScreen() {
+ return (
+
+
+
+ Connecting to React Native…
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ backgroundColor: '#0b0b0f',
+ },
+ center: {
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ gap: 12,
+ },
+ centerText: {
+ color: '#d6d6d6',
+ fontSize: 14,
+ },
+});
diff --git a/packages/file-system-plugin/src/ui/DetailLine.tsx b/packages/file-system-plugin/src/ui/DetailLine.tsx
new file mode 100644
index 00000000..003a3dd4
--- /dev/null
+++ b/packages/file-system-plugin/src/ui/DetailLine.tsx
@@ -0,0 +1,37 @@
+import React from 'react';
+import { StyleSheet, Text, View } from 'react-native';
+
+type DetailLineProps = {
+ label: string;
+ value: string;
+};
+
+export function DetailLine({ label, value }: DetailLineProps) {
+ return (
+
+ {label}
+
+ {value}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ detailLine: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ gap: 10,
+ },
+ detailLabel: {
+ color: '#9a9aa7',
+ fontSize: 11,
+ },
+ detailValue: {
+ color: '#eaeaf2',
+ fontSize: 11,
+ fontWeight: '600',
+ maxWidth: '70%',
+ textAlign: 'right',
+ },
+});
diff --git a/packages/file-system-plugin/src/ui/DetailPanel.tsx b/packages/file-system-plugin/src/ui/DetailPanel.tsx
new file mode 100644
index 00000000..15c5cf2a
--- /dev/null
+++ b/packages/file-system-plugin/src/ui/DetailPanel.tsx
@@ -0,0 +1,269 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import {
+ ActivityIndicator,
+ Image,
+ ScrollView,
+ StyleSheet,
+ Text,
+ View,
+} from 'react-native';
+import type { FsEntry } from '../shared/protocol';
+import { isLikelyImageFile } from '../shared/path';
+import { formatBytes, formatDate } from '../utils';
+import { formatTextPreview } from '../formatters';
+import { DetailLine } from './DetailLine';
+import { PathDisplay } from './PathDisplay';
+import type { useFileSystemRequests } from '../use-file-system-requests';
+
+type FileSystemRequests = ReturnType;
+
+type DetailPanelProps = {
+ selected: FsEntry | null;
+ requestImagePreview: FileSystemRequests['requestImagePreview'];
+ requestTextPreview: FileSystemRequests['requestTextPreview'];
+};
+
+export function DetailPanel({
+ selected,
+ requestImagePreview,
+ requestTextPreview,
+}: DetailPanelProps) {
+ const [imagePreviewUri, setImagePreviewUri] = useState(null);
+ const [imagePreviewError, setImagePreviewError] = useState(
+ null,
+ );
+ const [imagePreviewLoading, setImagePreviewLoading] = useState(false);
+
+ const [textPreview, setTextPreview] = useState(null);
+ const [textPreviewError, setTextPreviewError] = useState(null);
+ const [textPreviewLoading, setTextPreviewLoading] = useState(false);
+
+ // Load preview when selected file changes
+ const loadPreview = useCallback(
+ async (entry: FsEntry) => {
+ // Reset preview state
+ setImagePreviewUri(null);
+ setImagePreviewError(null);
+ setImagePreviewLoading(false);
+ setTextPreview(null);
+ setTextPreviewError(null);
+ setTextPreviewLoading(false);
+
+ if (entry.isDirectory) return;
+
+ // Handle image preview
+ if (isLikelyImageFile(entry.path)) {
+ setImagePreviewLoading(true);
+ try {
+ const res = await requestImagePreview(entry.path);
+ if (!res) return;
+ if (res.error || !res.dataUri) {
+ setImagePreviewError(res.error ?? 'No preview available');
+ return;
+ }
+ setImagePreviewUri(res.dataUri);
+ } catch (e) {
+ setImagePreviewError(e instanceof Error ? e.message : String(e));
+ } finally {
+ setImagePreviewLoading(false);
+ }
+ return;
+ }
+
+ // Handle text/file preview for any non-image file
+ setTextPreviewLoading(true);
+ try {
+ const res = await requestTextPreview(entry.path);
+ if (!res) return;
+ if (res.error || !res.content) {
+ setTextPreviewError(res.error ?? 'No preview available');
+ return;
+ }
+ setTextPreview(res.content);
+ } catch (e) {
+ setTextPreviewError(e instanceof Error ? e.message : String(e));
+ } finally {
+ setTextPreviewLoading(false);
+ }
+ },
+ [requestImagePreview, requestTextPreview],
+ );
+
+ useEffect(() => {
+ if (!selected || selected.isDirectory) {
+ // Reset previews when deselected or directory selected
+ setImagePreviewUri(null);
+ setImagePreviewError(null);
+ setImagePreviewLoading(false);
+ setTextPreview(null);
+ setTextPreviewError(null);
+ setTextPreviewLoading(false);
+ return;
+ }
+ loadPreview(selected);
+ }, [selected, loadPreview]);
+
+ return (
+
+ Details
+ {!selected ? (
+ Select a file or directory.
+ ) : (
+
+
+ {selected.name}
+
+
+
+
+
+
+
+
+ {!selected.isDirectory && isLikelyImageFile(selected.path) ? (
+
+ Image preview
+ {imagePreviewLoading ? (
+
+
+
+ Loading preview…
+
+
+ ) : imagePreviewError ? (
+ {imagePreviewError}
+ ) : imagePreviewUri ? (
+
+ ) : (
+
+ Tap the file again to re-fetch preview (limited size).
+
+ )}
+
+ ) : null}
+
+ {!selected.isDirectory && !isLikelyImageFile(selected.path) ? (
+
+ File preview
+ {textPreviewLoading ? (
+
+
+
+ Loading preview…
+
+
+ ) : textPreviewError ? (
+ {textPreviewError}
+ ) : textPreview ? (
+
+
+ {formatTextPreview(textPreview)}
+
+
+ ) : (
+
+ Tap the file again to re-fetch preview (limited size).
+
+ )}
+
+ ) : null}
+
+ )}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ detailPane: {
+ flex: 1,
+ padding: 16,
+ },
+ detailTitle: {
+ color: '#f2f2f2',
+ fontSize: 14,
+ fontWeight: '700',
+ marginBottom: 10,
+ },
+ detailEmpty: {
+ color: '#9a9aa7',
+ fontSize: 12,
+ },
+ detailCard: {
+ borderWidth: 1,
+ borderColor: '#1c1c24',
+ borderRadius: 12,
+ padding: 12,
+ backgroundColor: '#0f1016',
+ gap: 10,
+ },
+ detailName: {
+ color: '#f2f2f2',
+ fontSize: 13,
+ fontWeight: '700',
+ },
+ detailGrid: {
+ gap: 8,
+ },
+ previewBox: {
+ marginTop: 6,
+ borderTopWidth: 1,
+ borderTopColor: '#1c1c24',
+ paddingTop: 10,
+ gap: 8,
+ },
+ previewTitle: {
+ color: '#eaeaf2',
+ fontSize: 12,
+ fontWeight: '700',
+ },
+ previewHint: {
+ color: '#9a9aa7',
+ fontSize: 12,
+ },
+ previewError: {
+ color: '#ffb3c1',
+ fontSize: 12,
+ },
+ previewLoading: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 10,
+ },
+ previewLoadingText: {
+ color: '#cfcfe0',
+ fontSize: 12,
+ },
+ previewImage: {
+ width: '100%',
+ height: 240,
+ backgroundColor: '#0b0b0f',
+ borderRadius: 10,
+ },
+ textPreviewScroll: {
+ maxHeight: 300,
+ backgroundColor: '#0b0b0f',
+ borderRadius: 10,
+ padding: 12,
+ borderWidth: 1,
+ borderColor: '#1c1c24',
+ },
+ textPreviewContent: {
+ color: '#d6d6d6',
+ fontSize: 11,
+ fontFamily: 'Menlo',
+ lineHeight: 16,
+ },
+});
diff --git a/packages/file-system-plugin/src/ui/FileEntryRow.tsx b/packages/file-system-plugin/src/ui/FileEntryRow.tsx
new file mode 100644
index 00000000..facddf55
--- /dev/null
+++ b/packages/file-system-plugin/src/ui/FileEntryRow.tsx
@@ -0,0 +1,86 @@
+import React from 'react';
+import { Pressable, StyleSheet, Text, View } from 'react-native';
+import type { FsEntry } from '../shared/protocol';
+import { isLikelyImageFile, isLikelyTextFile } from '../shared/path';
+import { formatBytes, formatDate } from '../utils';
+import type { WebPressableState } from '../types';
+
+type FileEntryRowProps = {
+ entry: FsEntry;
+ isSelected: boolean;
+ onPress: (entry: FsEntry) => void;
+};
+
+function getFileIcon(entry: FsEntry): string {
+ if (entry.isDirectory) return '📁';
+ if (isLikelyImageFile(entry.path)) return '🖼️';
+ if (isLikelyTextFile(entry.path)) return '📝';
+ return '📄';
+}
+
+function FileEntryRowInner({ entry, isSelected, onPress }: FileEntryRowProps) {
+ return (
+ onPress(entry)}
+ style={(state: WebPressableState) => [
+ styles.row,
+ state.hovered && !isSelected && styles.rowHovered,
+ isSelected ? styles.rowSelected : undefined,
+ ]}
+ >
+ {getFileIcon(entry)}
+
+
+ {entry.name}
+
+
+ {entry.isDirectory
+ ? 'Directory'
+ : `${formatBytes(entry.size)} • ${formatDate(entry.modifiedAtMs)}`}
+
+
+ {entry.isDirectory ? '›' : ''}
+
+ );
+}
+
+export const FileEntryRow = React.memo(FileEntryRowInner);
+
+const styles = StyleSheet.create({
+ row: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 10,
+ paddingHorizontal: 16,
+ paddingVertical: 10,
+ },
+ rowHovered: {
+ backgroundColor: 'rgba(255, 255, 255, 0.04)',
+ },
+ rowSelected: {
+ backgroundColor: 'rgba(130, 50, 255, 0.12)',
+ },
+ rowIcon: {
+ width: 24,
+ textAlign: 'center',
+ fontSize: 16,
+ },
+ rowMain: {
+ flex: 1,
+ gap: 2,
+ },
+ rowName: {
+ color: '#f2f2f2',
+ fontSize: 13,
+ fontWeight: '600',
+ },
+ rowMeta: {
+ color: '#9a9aa7',
+ fontSize: 11,
+ },
+ rowChevron: {
+ color: '#6f6f7e',
+ fontSize: 18,
+ paddingLeft: 6,
+ },
+});
diff --git a/packages/file-system-plugin/src/ui/PathDisplay.tsx b/packages/file-system-plugin/src/ui/PathDisplay.tsx
new file mode 100644
index 00000000..f137e5ce
--- /dev/null
+++ b/packages/file-system-plugin/src/ui/PathDisplay.tsx
@@ -0,0 +1,162 @@
+import React, { useCallback, useState } from 'react';
+import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native';
+import type { WebPressableState } from '../types';
+import { copyToClipboard } from '../utils';
+
+type CopyState = 'idle' | 'copied' | 'error';
+
+type PathDisplayProps = {
+ path: string;
+ label?: string;
+};
+
+export function PathDisplay({ path, label }: PathDisplayProps) {
+ const [copyState, setCopyState] = useState('idle');
+
+ const handleCopy = useCallback(() => {
+ const success = copyToClipboard(path);
+ if (success) {
+ setCopyState('copied');
+ } else {
+ setCopyState('error');
+ }
+ setTimeout(() => setCopyState('idle'), 2000);
+ }, [path]);
+
+ const copyIcon =
+ copyState === 'copied' ? '✓' : copyState === 'error' ? '✕' : '⧉';
+ const copyLabel =
+ copyState === 'copied'
+ ? 'Copied!'
+ : copyState === 'error'
+ ? 'Failed'
+ : 'Copy';
+
+ return (
+
+ {label ? {label} : null}
+
+
+
+ {path}
+
+
+
+ [
+ styles.pathActionButton,
+ state.hovered && styles.pathActionButtonHovered,
+ copyState === 'copied' && styles.pathActionButtonSuccess,
+ copyState === 'error' && styles.pathActionButtonError,
+ ]}
+ onPress={handleCopy}
+ >
+
+ {copyIcon}
+
+
+ {copyLabel}
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ pathDisplayContainer: {
+ flex: 1,
+ gap: 6,
+ },
+ pathDisplayLabel: {
+ color: '#9a9aa7',
+ fontSize: 11,
+ fontWeight: '600',
+ },
+ pathDisplayRow: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 8,
+ backgroundColor: '#0b0b0f',
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: '#1c1c24',
+ paddingLeft: 10,
+ paddingVertical: 2,
+ overflow: 'hidden',
+ },
+ pathDisplayScroll: {
+ flex: 1,
+ maxHeight: 32,
+ },
+ pathDisplayScrollContent: {
+ alignItems: 'center',
+ paddingVertical: 6,
+ },
+ pathDisplayText: {
+ color: '#b8b8c8',
+ fontSize: 11,
+ fontFamily: 'Menlo',
+ },
+ pathDisplayActions: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ },
+ pathActionButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ gap: 4,
+ paddingHorizontal: 10,
+ paddingVertical: 8,
+ borderLeftWidth: 1,
+ borderLeftColor: '#2a2b37',
+ },
+ pathActionButtonHovered: {
+ backgroundColor: 'rgba(255, 255, 255, 0.06)',
+ },
+ pathActionButtonSuccess: {
+ backgroundColor: 'rgba(74, 222, 128, 0.1)',
+ },
+ pathActionButtonError: {
+ backgroundColor: 'rgba(255, 99, 132, 0.1)',
+ },
+ pathActionIcon: {
+ color: '#9a9aa7',
+ fontSize: 12,
+ },
+ pathActionIconSuccess: {
+ color: '#4ade80',
+ },
+ pathActionIconError: {
+ color: '#ff6384',
+ },
+ pathActionLabel: {
+ color: '#9a9aa7',
+ fontSize: 10,
+ fontWeight: '600',
+ },
+ pathActionLabelSuccess: {
+ color: '#4ade80',
+ },
+ pathActionLabelError: {
+ color: '#ff6384',
+ },
+});
diff --git a/packages/file-system-plugin/src/ui/TopBar.tsx b/packages/file-system-plugin/src/ui/TopBar.tsx
new file mode 100644
index 00000000..8612bacf
--- /dev/null
+++ b/packages/file-system-plugin/src/ui/TopBar.tsx
@@ -0,0 +1,244 @@
+import React, { useMemo } from 'react';
+import {
+ Pressable,
+ StyleSheet,
+ Text,
+ TextInput,
+ View,
+} from 'react-native';
+import type { FileSystemProvider, FsRoots } from '../shared/protocol';
+import type { WebPressableState } from '../types';
+
+type TopBarProps = {
+ provider: FileSystemProvider;
+ roots: FsRoots['roots'];
+ pathInput: string;
+ setPathInput: (value: string) => void;
+ currentPath: string;
+ setCurrentPath: (path: string) => void;
+ canGoBack: boolean;
+ onGo: () => void;
+ onBack: () => void;
+ onReload: () => void;
+};
+
+function getProviderLabel(provider: FileSystemProvider): string {
+ if (provider === 'expo') return 'Expo FileSystem';
+ if (provider === 'rnfs') return 'react-native-fs';
+ return 'No provider';
+}
+
+export function TopBar({
+ provider,
+ roots,
+ pathInput,
+ setPathInput,
+ currentPath,
+ setCurrentPath,
+ canGoBack,
+ onGo,
+ onBack,
+ onReload,
+}: TopBarProps) {
+ const rootChips = useMemo(() => {
+ return roots.map((r) => {
+ const isActive = r.path === currentPath;
+ return (
+ [
+ styles.chip,
+ state.hovered && !isActive && styles.chipHovered,
+ isActive ? styles.chipActive : undefined,
+ ]}
+ onPress={() => setCurrentPath(r.path)}
+ >
+
+ {r.label}
+
+
+ );
+ });
+ }, [currentPath, roots, setCurrentPath]);
+
+ return (
+
+
+ File System
+ {getProviderLabel(provider)}
+
+
+
+
+ [
+ styles.iconButton,
+ state.hovered && canGoBack && styles.iconButtonHovered,
+ !canGoBack ? styles.buttonDisabled : null,
+ ]}
+ onPress={onBack}
+ disabled={!canGoBack}
+ >
+ ←
+
+
+ [
+ styles.iconButton,
+ state.hovered && !!currentPath && styles.iconButtonHovered,
+ !currentPath ? styles.buttonDisabled : null,
+ ]}
+ onPress={onReload}
+ disabled={!currentPath}
+ >
+ ↻
+
+
+
+
+
+
+ [
+ styles.primaryButton,
+ state.hovered && styles.primaryButtonHovered,
+ ]}
+ onPress={onGo}
+ >
+ Go
+
+
+
+ {rootChips}
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ topBar: {
+ paddingHorizontal: 16,
+ paddingTop: 12,
+ paddingBottom: 10,
+ borderBottomWidth: 1,
+ borderBottomColor: '#1c1c24',
+ backgroundColor: '#0f1016',
+ },
+ topBarLeft: {
+ flexDirection: 'row',
+ alignItems: 'baseline',
+ justifyContent: 'space-between',
+ marginBottom: 10,
+ },
+ title: {
+ fontSize: 18,
+ fontWeight: '700',
+ color: '#f2f2f2',
+ },
+ statusText: {
+ fontSize: 12,
+ color: '#9a9aa7',
+ },
+ controls: {
+ gap: 10,
+ },
+ controlsRow: {
+ flexDirection: 'row',
+ gap: 8,
+ alignItems: 'center',
+ },
+ chipsRow: {
+ flexDirection: 'row',
+ gap: 8,
+ flexWrap: 'wrap',
+ },
+ iconButton: {
+ width: 36,
+ height: 36,
+ borderRadius: 8,
+ backgroundColor: '#1b1c25',
+ borderWidth: 1,
+ borderColor: '#2a2b37',
+ alignItems: 'center',
+ justifyContent: 'center',
+ },
+ iconButtonHovered: {
+ backgroundColor: '#252633',
+ },
+ iconButtonText: {
+ color: '#e9e9ee',
+ fontSize: 16,
+ fontWeight: '600',
+ },
+ buttonDisabled: {
+ opacity: 0.4,
+ },
+ primaryButton: {
+ paddingHorizontal: 12,
+ paddingVertical: 8,
+ borderRadius: 8,
+ backgroundColor: '#8232ff',
+ },
+ primaryButtonHovered: {
+ backgroundColor: '#9550ff',
+ },
+ primaryButtonText: {
+ color: '#ffffff',
+ fontSize: 12,
+ fontWeight: '700',
+ },
+ pathInputWrap: {
+ flex: 1,
+ minWidth: 240,
+ },
+ pathInput: {
+ paddingHorizontal: 10,
+ paddingVertical: 8,
+ borderRadius: 8,
+ borderWidth: 1,
+ borderColor: '#2a2b37',
+ backgroundColor: '#0b0b0f',
+ color: '#f2f2f2',
+ fontSize: 12,
+ },
+ chip: {
+ paddingHorizontal: 10,
+ paddingVertical: 6,
+ borderRadius: 999,
+ backgroundColor: '#161723',
+ borderWidth: 1,
+ borderColor: '#2a2b37',
+ maxWidth: 220,
+ },
+ chipHovered: {
+ backgroundColor: '#1e1f2e',
+ borderColor: '#3a3b4a',
+ },
+ chipActive: {
+ borderColor: '#8232ff',
+ backgroundColor: 'rgba(130, 50, 255, 0.14)',
+ },
+ chipText: {
+ color: '#cfcfe0',
+ fontSize: 12,
+ fontWeight: '600',
+ },
+ chipTextActive: {
+ color: '#efe7ff',
+ },
+});
diff --git a/packages/file-system-plugin/src/use-file-system-navigation.ts b/packages/file-system-plugin/src/use-file-system-navigation.ts
new file mode 100644
index 00000000..7c9c4d65
--- /dev/null
+++ b/packages/file-system-plugin/src/use-file-system-navigation.ts
@@ -0,0 +1,156 @@
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import type { RozeniteDevToolsClient } from '@rozenite/plugin-bridge';
+import type {
+ FileSystemEventMap,
+ FileSystemProvider,
+ FsEntry,
+ FsRoots,
+} from './shared/protocol';
+import { parentPath } from './shared/path';
+import type { useFileSystemRequests } from './use-file-system-requests';
+
+type FileSystemRequests = ReturnType;
+
+export function useFileSystemNavigation(
+ client: RozeniteDevToolsClient | null,
+ requests: FileSystemRequests,
+) {
+ const [provider, setProvider] = useState('none');
+ const [roots, setRoots] = useState([]);
+ const [pathInput, setPathInput] = useState('');
+ const [currentPath, setCurrentPath] = useState('');
+
+ const [entries, setEntries] = useState([]);
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ const loadRootsAndMaybeInit = useCallback(async () => {
+ setError(null);
+ const res = await requests.requestRoots();
+ if (!res) return;
+ setProvider(res.provider);
+ if (res.error) {
+ setRoots([]);
+ setError(res.error);
+ return;
+ }
+ setRoots(res.roots);
+
+ // Initialize path to first root only once
+ if (!currentPath && res.roots.length > 0) {
+ const first = res.roots[0]!.path;
+ setPathInput(first);
+ setCurrentPath(first);
+ }
+ }, [currentPath, requests.requestRoots]);
+
+ useEffect(() => {
+ if (!client) return;
+ loadRootsAndMaybeInit();
+ }, [client, loadRootsAndMaybeInit]);
+
+ const loadDir = useCallback(
+ async (path: string) => {
+ setLoading(true);
+ setError(null);
+
+ try {
+ const res = await requests.requestList(path);
+ if (!res) return;
+ setProvider(res.provider);
+ setCurrentPath(res.path);
+ setPathInput(res.path);
+ if (res.error) {
+ setEntries([]);
+ setError(res.error);
+ return;
+ }
+ setEntries(res.entries);
+ } catch (e) {
+ setEntries([]);
+ setError(e instanceof Error ? e.message : String(e));
+ } finally {
+ setLoading(false);
+ }
+ },
+ [requests.requestList],
+ );
+
+ useEffect(() => {
+ if (!client) return;
+ if (!currentPath) return;
+ loadDir(currentPath);
+ }, [client, currentPath]);
+
+ // Handle RN app ready/reconnect - re-fetch data when RN side (re)initializes
+ useEffect(() => {
+ if (!client) return;
+
+ const subReady = client.onMessage('fs:ready', async () => {
+ // Reset UI state on reconnect
+ setProvider('none');
+ setRoots([]);
+ setEntries([]);
+ setError(null);
+
+ // Re-fetch roots
+ await loadRootsAndMaybeInit();
+
+ // If user was in a directory, reload it
+ if (currentPath) {
+ loadDir(currentPath);
+ }
+ });
+
+ return () => {
+ subReady.remove();
+ };
+ }, [client, currentPath, loadDir, loadRootsAndMaybeInit]);
+
+ const onGo = useCallback(() => {
+ const next = pathInput.trim();
+ if (!next) return;
+ setCurrentPath(next);
+ }, [pathInput]);
+
+ // Check if we can go back (not at or above a root path)
+ const canGoBack = useMemo(() => {
+ if (!currentPath) return false;
+ // If current path is one of the roots, we can't go up
+ const isAtRoot = roots.some((r) => r.path === currentPath);
+ if (isAtRoot) return false;
+ // Check if parent path exists and we wouldn't go above a root
+ const parent = parentPath(currentPath);
+ if (!parent) return false;
+ // Ensure we're still within one of the root paths
+ return roots.some((r) => parent.startsWith(r.path) || parent === r.path);
+ }, [currentPath, roots]);
+
+ const onBack = useCallback(() => {
+ if (!canGoBack) return;
+ const p = parentPath(currentPath);
+ if (!p) return;
+ setCurrentPath(p);
+ }, [currentPath, canGoBack]);
+
+ const onReload = useCallback(() => {
+ if (!currentPath) return;
+ loadDir(currentPath);
+ }, [currentPath, loadDir]);
+
+ return {
+ provider,
+ roots,
+ pathInput,
+ setPathInput,
+ currentPath,
+ setCurrentPath,
+ entries,
+ loading,
+ error,
+ canGoBack,
+ onGo,
+ onBack,
+ onReload,
+ };
+}
diff --git a/packages/file-system-plugin/src/use-file-system-requests.ts b/packages/file-system-plugin/src/use-file-system-requests.ts
new file mode 100644
index 00000000..01513320
--- /dev/null
+++ b/packages/file-system-plugin/src/use-file-system-requests.ts
@@ -0,0 +1,131 @@
+import { useCallback, useEffect, useRef } from 'react';
+import type { RozeniteDevToolsClient } from '@rozenite/plugin-bridge';
+import type { FileSystemEventMap } from './shared/protocol';
+import { newRequestId, withTimeout } from './utils';
+
+type PendingResolvers = {
+ roots: Map<
+ string,
+ (payload: FileSystemEventMap['fs:get-roots:result']) => void
+ >;
+ list: Map void>;
+ image: Map<
+ string,
+ (payload: FileSystemEventMap['fs:read-image:result']) => void
+ >;
+ file: Map<
+ string,
+ (payload: FileSystemEventMap['fs:read-file:result']) => void
+ >;
+};
+
+export function useFileSystemRequests(
+ client: RozeniteDevToolsClient | null,
+) {
+ const pendingRef = useRef({
+ roots: new Map(),
+ list: new Map(),
+ image: new Map(),
+ file: new Map(),
+ });
+
+ useEffect(() => {
+ if (!client) return;
+
+ const subRoots = client.onMessage('fs:get-roots:result', (payload) => {
+ const resolve = pendingRef.current.roots.get(payload.requestId);
+ if (resolve) {
+ pendingRef.current.roots.delete(payload.requestId);
+ resolve(payload);
+ }
+ });
+
+ const subList = client.onMessage('fs:list:result', (payload) => {
+ const resolve = pendingRef.current.list.get(payload.requestId);
+ if (resolve) {
+ pendingRef.current.list.delete(payload.requestId);
+ resolve(payload);
+ }
+ });
+
+ const subImage = client.onMessage('fs:read-image:result', (payload) => {
+ const resolve = pendingRef.current.image.get(payload.requestId);
+ if (resolve) {
+ pendingRef.current.image.delete(payload.requestId);
+ resolve(payload);
+ }
+ });
+
+ const subFile = client.onMessage('fs:read-file:result', (payload) => {
+ const resolve = pendingRef.current.file.get(payload.requestId);
+ if (resolve) {
+ pendingRef.current.file.delete(payload.requestId);
+ resolve(payload);
+ }
+ });
+
+ return () => {
+ subRoots.remove();
+ subList.remove();
+ subImage.remove();
+ subFile.remove();
+ };
+ }, [client]);
+
+ const requestRoots = useCallback(async () => {
+ if (!client) return null;
+ const requestId = newRequestId();
+ const p = new Promise(
+ (resolve) => {
+ pendingRef.current.roots.set(requestId, resolve);
+ },
+ );
+ client.send('fs:get-roots', { requestId });
+ return await withTimeout(p, 8000, 'Timeout fetching roots');
+ }, [client]);
+
+ const requestList = useCallback(
+ async (path: string) => {
+ if (!client) return null;
+ const requestId = newRequestId();
+ const p = new Promise((resolve) => {
+ pendingRef.current.list.set(requestId, resolve);
+ });
+ client.send('fs:list', { requestId, path });
+ return await withTimeout(p, 15000, 'Timeout listing directory');
+ },
+ [client],
+ );
+
+ const requestImagePreview = useCallback(
+ async (path: string, maxBytes = 10_000_000) => {
+ if (!client) return null;
+ const requestId = newRequestId();
+ const p = new Promise(
+ (resolve) => {
+ pendingRef.current.image.set(requestId, resolve);
+ },
+ );
+ client.send('fs:read-image', { requestId, path, maxBytes });
+ return await withTimeout(p, 15000, 'Timeout reading image');
+ },
+ [client],
+ );
+
+ const requestTextPreview = useCallback(
+ async (path: string, maxBytes = 10_000_000) => {
+ if (!client) return null;
+ const requestId = newRequestId();
+ const p = new Promise(
+ (resolve) => {
+ pendingRef.current.file.set(requestId, resolve);
+ },
+ );
+ client.send('fs:read-file', { requestId, path, maxBytes });
+ return await withTimeout(p, 15000, 'Timeout reading file');
+ },
+ [client],
+ );
+
+ return { requestRoots, requestList, requestImagePreview, requestTextPreview };
+}
diff --git a/packages/file-system-plugin/src/utils.ts b/packages/file-system-plugin/src/utils.ts
new file mode 100644
index 00000000..57c521ef
--- /dev/null
+++ b/packages/file-system-plugin/src/utils.ts
@@ -0,0 +1,62 @@
+export function newRequestId(): string {
+ return `${Date.now().toString(16)}-${Math.random().toString(16).slice(2)}`;
+}
+
+export function formatBytes(bytes?: number | null): string {
+ if (bytes == null) return '—';
+ if (bytes < 1024) return `${bytes} B`;
+ const kb = bytes / 1024;
+ if (kb < 1024) return `${kb.toFixed(1)} KB`;
+ const mb = kb / 1024;
+ if (mb < 1024) return `${mb.toFixed(1)} MB`;
+ const gb = mb / 1024;
+ return `${gb.toFixed(1)} GB`;
+}
+
+export function formatDate(ms?: number | null): string {
+ if (!ms) return '—';
+ try {
+ return new Date(ms).toLocaleString();
+ } catch {
+ return '—';
+ }
+}
+
+export async function withTimeout(
+ p: Promise,
+ ms: number,
+ message: string,
+): Promise {
+ let timeout: ReturnType | null = null;
+ try {
+ return await Promise.race([
+ p,
+ new Promise((_, reject) => {
+ timeout = setTimeout(() => reject(new Error(message)), ms);
+ }),
+ ]);
+ } finally {
+ if (timeout) clearTimeout(timeout);
+ }
+}
+
+// Helper to copy text to clipboard (works in web views)
+export function copyToClipboard(text: string): boolean {
+ try {
+ // Use the textarea fallback approach which is most reliable
+ const textArea = document.createElement('textarea');
+ textArea.value = text;
+ textArea.style.position = 'fixed';
+ textArea.style.top = '0';
+ textArea.style.left = '-9999px';
+ textArea.style.opacity = '0';
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+ const success = document.execCommand('copy');
+ document.body.removeChild(textArea);
+ return success;
+ } catch {
+ return false;
+ }
+}
diff --git a/packages/file-system-plugin/tailwind.config.ts b/packages/file-system-plugin/tailwind.config.ts
new file mode 100644
index 00000000..d756b75a
--- /dev/null
+++ b/packages/file-system-plugin/tailwind.config.ts
@@ -0,0 +1,94 @@
+import type { Config } from 'tailwindcss';
+
+const config: Config = {
+ darkMode: ['class'],
+ content: ['./src/ui/**/*.{js,ts,jsx,tsx,mdx}'],
+ theme: {
+ extend: {
+ translate: {
+ '0.75': '0.1875rem',
+ },
+ colors: {
+ background: 'hsl(var(--background))',
+ foreground: 'hsl(var(--foreground))',
+ card: {
+ DEFAULT: 'hsl(var(--card))',
+ foreground: 'hsl(var(--card-foreground))',
+ },
+ popover: {
+ DEFAULT: 'hsl(var(--popover))',
+ foreground: 'hsl(var(--popover-foreground))',
+ },
+ primary: {
+ DEFAULT: 'hsl(var(--primary))',
+ foreground: 'hsl(var(--primary-foreground))',
+ },
+ secondary: {
+ DEFAULT: 'hsl(var(--secondary))',
+ foreground: 'hsl(var(--secondary-foreground))',
+ },
+ muted: {
+ DEFAULT: 'hsl(var(--muted))',
+ foreground: 'hsl(var(--muted-foreground))',
+ },
+ accent: {
+ DEFAULT: 'hsl(var(--accent))',
+ foreground: 'hsl(var(--accent-foreground))',
+ },
+ destructive: {
+ DEFAULT: 'hsl(var(--destructive))',
+ foreground: 'hsl(var(--destructive-foreground))',
+ },
+ border: 'hsl(var(--border))',
+ input: 'hsl(var(--input))',
+ ring: 'hsl(var(--ring))',
+ chart: {
+ '1': 'hsl(var(--chart-1))',
+ '2': 'hsl(var(--chart-2))',
+ '3': 'hsl(var(--chart-3))',
+ '4': 'hsl(var(--chart-4))',
+ '5': 'hsl(var(--chart-5))',
+ },
+ sidebar: {
+ DEFAULT: 'hsl(var(--sidebar-background))',
+ foreground: 'hsl(var(--sidebar-foreground))',
+ primary: 'hsl(var(--sidebar-primary))',
+ 'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
+ accent: 'hsl(var(--sidebar-accent))',
+ 'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
+ border: 'hsl(var(--sidebar-border))',
+ ring: 'hsl(var(--sidebar-ring))',
+ },
+ },
+ borderRadius: {
+ lg: 'var(--radius)',
+ md: 'calc(var(--radius) - 2px)',
+ sm: 'calc(var(--radius) - 4px)',
+ },
+ keyframes: {
+ 'accordion-down': {
+ from: {
+ height: '0',
+ },
+ to: {
+ height: 'var(--radix-accordion-content-height)',
+ },
+ },
+ 'accordion-up': {
+ from: {
+ height: 'var(--radix-accordion-content-height)',
+ },
+ to: {
+ height: '0',
+ },
+ },
+ },
+ animation: {
+ 'accordion-down': 'accordion-down 0.2s ease-out',
+ 'accordion-up': 'accordion-up 0.2s ease-out',
+ },
+ },
+ },
+ plugins: [require('tailwindcss-animate')],
+};
+export default config;
diff --git a/packages/file-system-plugin/tsconfig.json b/packages/file-system-plugin/tsconfig.json
new file mode 100644
index 00000000..a9ea94bd
--- /dev/null
+++ b/packages/file-system-plugin/tsconfig.json
@@ -0,0 +1,32 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "noFallthroughCasesInSwitch": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src/**/*", "react-native.ts", "rozenite.config.ts"],
+ "exclude": ["node_modules", "dist", "build"],
+ "references": [
+ {
+ "path": "../plugin-bridge"
+ },
+ {
+ "path": "../cli"
+ },
+ {
+ "path": "../vite-plugin"
+ }
+ ]
+}
diff --git a/packages/file-system-plugin/vite.config.ts b/packages/file-system-plugin/vite.config.ts
new file mode 100644
index 00000000..7db548c7
--- /dev/null
+++ b/packages/file-system-plugin/vite.config.ts
@@ -0,0 +1,20 @@
+///
+import { defineConfig } from 'vite';
+import { rozenitePlugin } from '@rozenite/vite-plugin';
+
+export default defineConfig({
+ root: __dirname,
+ plugins: [rozenitePlugin()],
+ base: './',
+ build: {
+ outDir: './dist',
+ emptyOutDir: false,
+ reportCompressedSize: false,
+ minify: true,
+ sourcemap: false,
+ },
+ server: {
+ port: 3000,
+ open: true,
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b009c259..8909b4ba 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -178,6 +178,9 @@ importers:
apps/playground:
dependencies:
+ '@dr.pogodin/react-native-fs':
+ specifier: ^2.36.2
+ version: 2.36.2(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
'@react-navigation/bottom-tabs':
specifier: ^7.4.7
version: 7.4.7(@react-navigation/native@7.1.14(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.5.2(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-screens@4.4.0(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
@@ -196,6 +199,9 @@ importers:
'@rozenite/expo-atlas-plugin':
specifier: workspace:*
version: link:../../packages/expo-atlas-plugin
+ '@rozenite/file-system-plugin':
+ specifier: workspace:*
+ version: link:../../packages/file-system-plugin
'@rozenite/mmkv-plugin':
specifier: workspace:*
version: link:../../packages/mmkv-plugin
@@ -353,6 +359,55 @@ importers:
specifier: ^6.0.0
version: 6.3.5(@types/node@22.17.0)(jiti@2.6.1)(terser@5.43.1)(yaml@2.8.1)
+ packages/file-system-plugin:
+ dependencies:
+ '@rozenite/plugin-bridge':
+ specifier: workspace:*
+ version: link:../plugin-bridge
+ devDependencies:
+ '@rozenite/vite-plugin':
+ specifier: workspace:*
+ version: link:../vite-plugin
+ '@types/react':
+ specifier: ~18.3.12
+ version: 18.3.23
+ autoprefixer:
+ specifier: ^10.4.21
+ version: 10.4.21(postcss@8.5.6)
+ lucide-react:
+ specifier: ^0.263.1
+ version: 0.263.1(react@18.3.1)
+ postcss:
+ specifier: ^8.5.6
+ version: 8.5.6
+ react:
+ specifier: 18.3.1
+ version: 18.3.1
+ react-dom:
+ specifier: 18.3.0
+ version: 18.3.0(react@18.3.1)
+ react-native:
+ specifier: 0.76.0
+ version: 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)
+ react-native-web:
+ specifier: 0.21.0
+ version: 0.21.0(encoding@0.1.13)(react-dom@18.3.0(react@18.3.1))(react@18.3.1)
+ rozenite:
+ specifier: workspace:*
+ version: link:../cli
+ tailwindcss:
+ specifier: ^3.4.17
+ version: 3.4.17
+ tailwindcss-animate:
+ specifier: ^1.0.7
+ version: 1.0.7(tailwindcss@3.4.17)
+ typescript:
+ specifier: ^5.7.3
+ version: 5.8.3
+ vite:
+ specifier: ^6.0.0
+ version: 6.3.5(@types/node@22.17.0)(jiti@2.6.1)(terser@5.43.1)(yaml@2.8.1)
+
packages/metro:
dependencies:
'@rozenite/middleware':
@@ -481,31 +536,31 @@ importers:
devDependencies:
'@floating-ui/react':
specifier: ^0.26.0
- version: 0.26.28(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ version: 0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-dropdown-menu':
specifier: ^2.1.15
- version: 2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ version: 2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-scroll-area':
specifier: ^1.2.9
- version: 1.2.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ version: 1.2.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-separator':
specifier: ^1.1.7
- version: 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ version: 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot':
specifier: ^1.2.3
version: 1.2.3(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-tabs':
specifier: ^1.1.12
- version: 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ version: 1.1.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@rozenite/vite-plugin':
specifier: workspace:*
version: link:../vite-plugin
'@tanstack/react-table':
specifier: ^8.21.3
- version: 8.21.3(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@tanstack/react-virtual':
specifier: ^3.0.0
- version: 3.13.12(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ version: 3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@types/react':
specifier: ~18.3.23
version: 18.3.23
@@ -813,7 +868,7 @@ importers:
devDependencies:
'@callstack/repack':
specifier: ^5.2
- version: 5.2.0(@babel/core@7.28.0)(@module-federation/enhanced@0.20.0(@rspack/core@1.5.8(@swc/helpers@0.5.17))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))))(@rspack/core@1.5.8(@swc/helpers@0.5.17))(@swc/core@1.5.29(@swc/helpers@0.5.17))(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@19.1.1))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))
+ version: 5.2.0(@babel/core@7.28.0)(@module-federation/enhanced@0.20.0(@rspack/core@1.5.8(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))))(@rspack/core@1.5.8(@swc/helpers@0.5.17))(@swc/core@1.5.29(@swc/helpers@0.5.17))(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))
'@types/semver':
specifier: ^7.7.0
version: 7.7.0
@@ -1024,7 +1079,7 @@ packages:
'@apollo/server@4.12.2':
resolution: {integrity: sha512-jKRlf+sBMMdKYrjMoiWKne42Eb6paBfDOr08KJnUaeaiyWFj+/040FjVPQI7YGLfdwnYIsl1NUUqS2UdgezJDg==}
engines: {node: '>=14.16.0'}
- deprecated: Apollo Server v4 is deprecated and will transition to end-of-life on January 26, 2026. As long as you are already using a non-EOL version of Node.js, upgrading to v5 should take only a few minutes. See https://www.apollographql.com/docs/apollo-server/previous-versions for details.
+ deprecated: Apollo Server v4 is end-of-life since January 26, 2026. As long as you are already using a non-EOL version of Node.js, upgrading to v5 should take only a few minutes. See https://www.apollographql.com/docs/apollo-server/previous-versions for details.
peerDependencies:
graphql: ^16.6.0
@@ -1991,6 +2046,12 @@ packages:
peerDependencies:
react: '>=16.8.0'
+ '@dr.pogodin/react-native-fs@2.36.2':
+ resolution: {integrity: sha512-n3V3G7L7BNbKC2bUfRLIdT0H283Q8SxsKhly+5BrCdVDtdgem2XJxYOBjClUuJSpDN6zivjTkHIrj/LUMJ4WDQ==}
+ peerDependencies:
+ react: '*'
+ react-native: 0.76.0
+
'@electron/get@2.0.3':
resolution: {integrity: sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==}
engines: {node: '>=12'}
@@ -3626,8 +3687,6 @@ packages:
'@react-native/babel-preset@0.76.0':
resolution: {integrity: sha512-HgQt4MyuWLcnrIglXn7GNPPVwtzZ4ffX+SUisdhmPtJCHuP8AOU3HsgOKLhqVfEGWTBlE4kbWoTmmLU87IJaOw==}
engines: {node: '>=18'}
- peerDependencies:
- '@babel/core': '*'
'@react-native/babel-preset@0.76.9':
resolution: {integrity: sha512-TbSeCplCM6WhL3hR2MjC/E1a9cRnMLz7i767T7mP90oWkklEjyPxWl+0GGoVGnJ8FC/jLUupg/HvREKjjif6lw==}
@@ -3679,8 +3738,6 @@ packages:
'@react-native/metro-babel-transformer@0.76.0':
resolution: {integrity: sha512-aq0MrjaOxDitSqQbttBcOt+5tjemCabhEX2gGthy8cNeZokBa2raoHQInDo9iBBN1ePKDCwKGypyC8zKA5dksQ==}
engines: {node: '>=18'}
- peerDependencies:
- '@babel/core': '*'
'@react-native/metro-babel-transformer@0.76.9':
resolution: {integrity: sha512-HGq11347UHNiO/NvVbAO35hQCmH8YZRs7in7nVq7SL99pnpZK4WXwLdAXmSuwz5uYqOuwnKYDlpadz8fkE94Mg==}
@@ -5558,6 +5615,9 @@ packages:
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+ buffer@6.0.3:
+ resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+
bundle-name@4.1.0:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
@@ -7139,15 +7199,16 @@ packages:
glob@10.4.5:
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.1.4:
resolution: {integrity: sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
global-agent@3.0.0:
resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==}
@@ -7363,6 +7424,9 @@ packages:
engines: {node: '>=12'}
hasBin: true
+ http-status-codes@2.3.0:
+ resolution: {integrity: sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==}
+
http2-wrapper@1.0.3:
resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==}
engines: {node: '>=10.19.0'}
@@ -11125,6 +11189,7 @@ packages:
whatwg-encoding@2.0.0:
resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==}
engines: {node: '>=12'}
+ deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-fetch@3.6.20:
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
@@ -12449,7 +12514,7 @@ snapshots:
- supports-color
- utf-8-validate
- '@callstack/repack@5.2.0(@babel/core@7.28.0)(@module-federation/enhanced@0.20.0(@rspack/core@1.5.8(@swc/helpers@0.5.17))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))))(@rspack/core@1.5.8(@swc/helpers@0.5.17))(@swc/core@1.5.29(@swc/helpers@0.5.17))(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@19.1.1))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))':
+ '@callstack/repack@5.2.0(@babel/core@7.28.0)(@module-federation/enhanced@0.20.0(@rspack/core@1.5.8(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))))(@rspack/core@1.5.8(@swc/helpers@0.5.17))(@swc/core@1.5.29(@swc/helpers@0.5.17))(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))':
dependencies:
'@callstack/repack-dev-server': 5.2.0
'@discoveryjs/json-ext': 0.5.7
@@ -12468,7 +12533,7 @@ snapshots:
memfs: 4.24.0
mime-types: 2.1.35
pretty-format: 26.6.2
- react-native: 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@19.1.1)
+ react-native: 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)
react-refresh: 0.14.2
schema-utils: 4.3.2
semver: 7.7.2
@@ -12478,7 +12543,7 @@ snapshots:
throttleit: 2.1.0
webpack-merge: 6.0.1
optionalDependencies:
- '@module-federation/enhanced': 0.20.0(@rspack/core@1.5.8(@swc/helpers@0.5.17))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))
+ '@module-federation/enhanced': 0.20.0(@rspack/core@1.5.8(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))
'@rspack/core': 1.5.8(@swc/helpers@0.5.17)
webpack: 5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))
transitivePeerDependencies:
@@ -12692,6 +12757,13 @@ snapshots:
react: 18.3.1
tslib: 2.8.1
+ '@dr.pogodin/react-native-fs@2.36.2(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
+ dependencies:
+ buffer: 6.0.3
+ http-status-codes: 2.3.0
+ react: 18.3.1
+ react-native: 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1)
+
'@electron/get@2.0.3':
dependencies:
debug: 4.4.1(supports-color@5.5.0)
@@ -12985,18 +13057,18 @@ snapshots:
react: 18.3.1
react-dom: 18.3.0(react@18.3.1)
- '@floating-ui/react-dom@2.1.4(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@floating-ui/react-dom@2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@floating-ui/dom': 1.7.2
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
- '@floating-ui/react@0.26.28(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@floating-ui/react@0.26.28(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@floating-ui/react-dom': 2.1.4(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@floating-ui/react-dom': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@floating-ui/utils': 0.2.10
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
tabbable: 6.2.0
'@floating-ui/utils@0.2.10': {}
@@ -13397,15 +13469,6 @@ snapshots:
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
- '@module-federation/data-prefetch@0.20.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
- dependencies:
- '@module-federation/runtime': 0.20.0
- '@module-federation/sdk': 0.20.0
- fs-extra: 9.1.0
- react: 19.1.1
- react-dom: 19.1.1(react@19.1.1)
- optional: true
-
'@module-federation/data-prefetch@0.9.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@module-federation/runtime': 0.9.1
@@ -13492,35 +13555,6 @@ snapshots:
- supports-color
- utf-8-validate
- '@module-federation/enhanced@0.20.0(@rspack/core@1.5.8(@swc/helpers@0.5.17))(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(typescript@5.8.3)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))':
- dependencies:
- '@module-federation/bridge-react-webpack-plugin': 0.20.0
- '@module-federation/cli': 0.20.0(typescript@5.8.3)
- '@module-federation/data-prefetch': 0.20.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
- '@module-federation/dts-plugin': 0.20.0(typescript@5.8.3)
- '@module-federation/error-codes': 0.20.0
- '@module-federation/inject-external-runtime-core-plugin': 0.20.0(@module-federation/runtime-tools@0.20.0)
- '@module-federation/managers': 0.20.0
- '@module-federation/manifest': 0.20.0(typescript@5.8.3)
- '@module-federation/rspack': 0.20.0(@rspack/core@1.5.8(@swc/helpers@0.5.17))(typescript@5.8.3)
- '@module-federation/runtime-tools': 0.20.0
- '@module-federation/sdk': 0.20.0
- btoa: 1.2.1
- schema-utils: 4.3.2
- upath: 2.0.1
- optionalDependencies:
- typescript: 5.8.3
- webpack: 5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17))
- transitivePeerDependencies:
- - '@rspack/core'
- - bufferutil
- - debug
- - react
- - react-dom
- - supports-color
- - utf-8-validate
- optional: true
-
'@module-federation/enhanced@0.9.1(@rspack/core@1.5.8(@swc/helpers@0.5.17))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3)(webpack@5.102.1(@swc/core@1.5.29(@swc/helpers@0.5.17)))':
dependencies:
'@module-federation/bridge-react-webpack-plugin': 0.9.1
@@ -14201,11 +14235,11 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14276,14 +14310,14 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14355,15 +14389,15 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14383,17 +14417,17 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-dropdown-menu@2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-menu': 2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-menu': 2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14415,13 +14449,13 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14503,27 +14537,27 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-menu@2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-menu@2.1.15(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-dismissable-layer': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-focus-guards': 1.1.2(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-popper': 1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-popper': 1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
aria-hidden: 1.2.6
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
react-remove-scroll: 2.7.1(@types/react@18.3.23)(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
@@ -14646,20 +14680,20 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-popper@1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-popper@1.2.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@floating-ui/react-dom': 2.1.4(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@floating-ui/react-dom': 2.1.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-size': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/rect': 1.1.1
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14674,12 +14708,12 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14694,12 +14728,12 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-presence@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-presence@1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14713,11 +14747,11 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/react-slot': 1.2.3(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14767,19 +14801,19 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
- '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14801,19 +14835,19 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-scroll-area@1.2.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-scroll-area@1.2.9(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/number': 1.1.1
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14856,11 +14890,11 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-separator@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-separator@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -14922,18 +14956,18 @@ snapshots:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
- '@radix-ui/react-tabs@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@radix-ui/react-tabs@1.1.12(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@radix-ui/primitive': 1.1.2
'@radix-ui/react-context': 1.1.2(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-direction': 1.1.1(@types/react@18.3.23)(react@18.3.1)
'@radix-ui/react-id': 1.1.1(@types/react@18.3.23)(react@18.3.1)
- '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
- '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@19.1.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-presence': 1.1.4(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ '@radix-ui/react-roving-focus': 1.1.10(@types/react-dom@18.3.7(@types/react@18.3.23))(@types/react@18.3.23)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.23)(react@18.3.1)
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
optionalDependencies:
'@types/react': 18.3.23
'@types/react-dom': 18.3.7(@types/react@18.3.23)
@@ -15250,7 +15284,7 @@ snapshots:
- '@babel/preset-env'
- supports-color
- '@react-native/babel-preset@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))':
+ '@react-native/babel-preset@0.76.0(@babel/preset-env@7.28.3(@babel/core@7.28.0))':
dependencies:
'@babel/core': 7.28.0
'@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.0)
@@ -15380,10 +15414,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@react-native/community-cli-plugin@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(encoding@0.1.13)':
+ '@react-native/community-cli-plugin@0.76.0(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(encoding@0.1.13)':
dependencies:
'@react-native/dev-middleware': 0.76.0
- '@react-native/metro-babel-transformer': 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))
+ '@react-native/metro-babel-transformer': 0.76.0(@babel/preset-env@7.28.3(@babel/core@7.28.0))
chalk: 4.1.2
execa: 5.1.1
invariant: 2.2.4
@@ -15395,7 +15429,6 @@ snapshots:
optionalDependencies:
'@react-native-community/cli-server-api': 15.0.1
transitivePeerDependencies:
- - '@babel/core'
- '@babel/preset-env'
- bufferutil
- encoding
@@ -15428,10 +15461,10 @@ snapshots:
'@react-native/js-polyfills@0.76.9': {}
- '@react-native/metro-babel-transformer@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))':
+ '@react-native/metro-babel-transformer@0.76.0(@babel/preset-env@7.28.3(@babel/core@7.28.0))':
dependencies:
'@babel/core': 7.28.0
- '@react-native/babel-preset': 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))
+ '@react-native/babel-preset': 0.76.0(@babel/preset-env@7.28.3(@babel/core@7.28.0))
hermes-parser: 0.23.1
nullthrows: 1.1.1
transitivePeerDependencies:
@@ -15457,7 +15490,9 @@ snapshots:
transitivePeerDependencies:
- '@babel/core'
- '@babel/preset-env'
+ - bufferutil
- supports-color
+ - utf-8-validate
'@react-native/normalize-colors@0.74.89': {}
@@ -15472,15 +15507,6 @@ snapshots:
optionalDependencies:
'@types/react': 18.3.23
- '@react-native/virtualized-lists@0.76.0(@types/react@18.3.23)(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@19.1.1))(react@19.1.1)':
- dependencies:
- invariant: 2.2.4
- nullthrows: 1.1.1
- react: 19.1.1
- react-native: 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@19.1.1)
- optionalDependencies:
- '@types/react': 18.3.23
-
'@react-navigation/bottom-tabs@7.4.7(@react-navigation/native@7.1.14(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.5.2(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-screens@4.4.0(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)':
dependencies:
'@react-navigation/elements': 2.6.4(@react-navigation/native@7.1.14(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native-safe-area-context@5.5.2(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1))(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@18.3.1))(react@18.3.1)
@@ -16682,17 +16708,17 @@ snapshots:
react: 18.3.1
react-dom: 18.3.0(react@18.3.1)
- '@tanstack/react-table@8.21.3(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@tanstack/table-core': 8.21.3
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
- '@tanstack/react-virtual@3.13.12(react-dom@19.1.1(react@18.3.1))(react@18.3.1)':
+ '@tanstack/react-virtual@3.13.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
'@tanstack/virtual-core': 3.13.12
react: 18.3.1
- react-dom: 19.1.1(react@18.3.1)
+ react-dom: 18.3.1(react@18.3.1)
'@tanstack/table-core@8.21.3': {}
@@ -17964,6 +17990,11 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
+ buffer@6.0.3:
+ dependencies:
+ base64-js: 1.5.1
+ ieee754: 1.2.1
+
bundle-name@4.1.0:
dependencies:
run-applescript: 7.0.0
@@ -20058,6 +20089,8 @@ snapshots:
- debug
- supports-color
+ http-status-codes@2.3.0: {}
+
http2-wrapper@1.0.3:
dependencies:
quick-lru: 5.1.1
@@ -23091,11 +23124,6 @@ snapshots:
react: 18.3.1
scheduler: 0.23.2
- react-dom@19.1.1(react@18.3.1):
- dependencies:
- react: 18.3.1
- scheduler: 0.26.0
-
react-dom@19.1.1(react@19.1.1):
dependencies:
react: 19.1.1
@@ -23234,7 +23262,7 @@ snapshots:
'@jest/create-cache-key-function': 29.7.0
'@react-native/assets-registry': 0.76.0
'@react-native/codegen': 0.76.0(@babel/preset-env@7.28.3(@babel/core@7.28.0))
- '@react-native/community-cli-plugin': 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(encoding@0.1.13)
+ '@react-native/community-cli-plugin': 0.76.0(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(encoding@0.1.13)
'@react-native/gradle-plugin': 0.76.0
'@react-native/js-polyfills': 0.76.0
'@react-native/normalize-colors': 0.76.0
@@ -23281,58 +23309,6 @@ snapshots:
- supports-color
- utf-8-validate
- react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@19.1.1):
- dependencies:
- '@jest/create-cache-key-function': 29.7.0
- '@react-native/assets-registry': 0.76.0
- '@react-native/codegen': 0.76.0(@babel/preset-env@7.28.3(@babel/core@7.28.0))
- '@react-native/community-cli-plugin': 0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(encoding@0.1.13)
- '@react-native/gradle-plugin': 0.76.0
- '@react-native/js-polyfills': 0.76.0
- '@react-native/normalize-colors': 0.76.0
- '@react-native/virtualized-lists': 0.76.0(@types/react@18.3.23)(react-native@0.76.0(@babel/core@7.28.0)(@babel/preset-env@7.28.3(@babel/core@7.28.0))(@react-native-community/cli-server-api@15.0.1)(@types/react@18.3.23)(encoding@0.1.13)(react@19.1.1))(react@19.1.1)
- abort-controller: 3.0.0
- anser: 1.4.10
- ansi-regex: 5.0.1
- babel-jest: 29.7.0(@babel/core@7.28.0)
- babel-plugin-syntax-hermes-parser: 0.23.1
- base64-js: 1.5.1
- chalk: 4.1.2
- commander: 12.1.0
- event-target-shim: 5.0.1
- flow-enums-runtime: 0.0.6
- glob: 7.2.3
- invariant: 2.2.4
- jest-environment-node: 29.7.0
- jsc-android: 250231.0.0
- memoize-one: 5.2.1
- metro-runtime: 0.81.5
- metro-source-map: 0.81.5
- mkdirp: 0.5.6
- nullthrows: 1.1.1
- pretty-format: 29.7.0
- promise: 8.3.0
- react: 19.1.1
- react-devtools-core: 5.3.2
- react-refresh: 0.14.2
- regenerator-runtime: 0.13.11
- scheduler: 0.24.0-canary-efb381bbf-20230505
- semver: 7.7.2
- stacktrace-parser: 0.1.11
- whatwg-fetch: 3.6.20
- ws: 6.2.3
- yargs: 17.7.2
- optionalDependencies:
- '@types/react': 18.3.23
- transitivePeerDependencies:
- - '@babel/core'
- - '@babel/preset-env'
- - '@react-native-community/cli-server-api'
- - bufferutil
- - encoding
- - supports-color
- - utf-8-validate
-
react-redux@9.2.0(@types/react@18.3.23)(react@18.3.1)(redux@5.0.1):
dependencies:
'@types/use-sync-external-store': 0.0.6