Matthias Nott
2026-03-07 af1543135d42adc2e97dc5243aeef7418cd3b00d
feat: dual address auto-switch, custom icon, notifications, image support

- Add local/remote address with automatic failover (try local first, fall back to remote)
- Replace default Expo icon with custom PAILot teal airplane icon (all variants)
- Add local notification system (ObjC native module + ChatContext integration)
- Add Wake-on-LAN as fire-and-forget (never blocks connection)
- Add image send/receive with camera and photo library picker
- Add image caption modal (WhatsApp-style full-screen preview)
- Add session drawer, theme context, and dark/light mode cycling
- Add message persistence and per-session message storage
10 files added
25 files modified
changed files
Notes/PAI.md patch | view | blame | history
Notes/TODO.md patch | view | blame | history
app.json patch | view | blame | history
app/_layout.tsx patch | view | blame | history
app/chat.tsx patch | view | blame | history
app/navigate.tsx patch | view | blame | history
app/settings.tsx patch | view | blame | history
assets/android-icon-background.png patch | view | blame | history
assets/android-icon-foreground.png patch | view | blame | history
assets/android-icon-monochrome.png patch | view | blame | history
assets/favicon.png patch | view | blame | history
assets/icon.png patch | view | blame | history
assets/splash-icon.png patch | view | blame | history
bun.lock patch | view | blame | history
components/SessionDrawer.tsx patch | view | blame | history
components/SessionPicker.tsx patch | view | blame | history
components/chat/CommandBar.tsx patch | view | blame | history
components/chat/ImageCaptionModal.tsx patch | view | blame | history
components/chat/ImageViewer.tsx patch | view | blame | history
components/chat/InputBar.tsx patch | view | blame | history
components/chat/MessageBubble.tsx patch | view | blame | history
components/chat/VoiceButton.tsx patch | view | blame | history
components/ui/IconButton.tsx patch | view | blame | history
components/ui/StatusDot.tsx patch | view | blame | history
contexts/ChatContext.tsx patch | view | blame | history
contexts/ConnectionContext.tsx patch | view | blame | history
contexts/ThemeContext.tsx patch | view | blame | history
memory/MEMORY.md patch | view | blame | history
package-lock.json patch | view | blame | history
package.json patch | view | blame | history
services/audio.ts patch | view | blame | history
services/notifications.ts patch | view | blame | history
services/websocket.ts patch | view | blame | history
services/wol.ts patch | view | blame | history
types/index.ts patch | view | blame | history
Notes/PAI.md
....@@ -0,0 +1,13 @@
1
+---
2
+pai:
3
+ slug: "pailot"
4
+ registered: "2026-03-05"
5
+ last_indexed: null
6
+ status: active
7
+---
8
+
9
+# pailot
10
+
11
+<!-- Everything below the YAML frontmatter is yours — PAI never modifies content here. -->
12
+<!-- Use this file for project notes, decisions, preferences, or anything you want. -->
13
+<!-- PAI only reads and updates the `pai:` block in the frontmatter above. -->
Notes/TODO.md
....@@ -0,0 +1,20 @@
1
+# PAILot TODO
2
+
3
+## Bugs
4
+
5
+- [ ] **Auto-scroll hijacks reading position** — `MessageList.tsx` fires `scrollToEnd` on every `onContentSizeChange` and on `messages.length` change. If you're scrolled up reading older messages, new incoming messages yank you to the bottom. Fix: only auto-scroll when already near the bottom (track scroll offset, threshold ~100px).
6
+- [ ] **Text not selectable in message bubbles** — `MessageBubble.tsx` uses plain `<Text>` without `selectable={true}`. Cannot long-press to select/copy text from AI responses or own messages.
7
+
8
+## UX Improvements
9
+
10
+- [ ] **Show session name on each message** — Currently each bubble only shows the time (e.g. "13:42"). Since PAILot runs parallel sessions on one screen, each message should also display which session it came from or went to (e.g. "13:42 · whazaa" or "13:42 · AIBroker"). Requires: add `sessionId`/`sessionName` to the `Message` type, have the gateway include session info with each message, render it in `MessageBubble.tsx` next to the timestamp.
11
+
12
+## Features
13
+
14
+- [ ] **Multimodal upload (send images/photos/video/audio from phone)** — The app can *receive* images and voice, but there's no UI to *send* media from the phone to the AI session. Needs: image picker / camera access, new `WsOutgoing` type for images/files, gateway handler to route media to the active session, and backend support to process multimodal input.
15
+- [ ] **Relay-based connectivity** — PAILot currently only works on the local network (WebSocket to `localhost:8765`). For use outside home network: self-hosted relay on a VPS with encrypted tunnel (Tailscale, cloudflared), or APNs push relay. See AIBroker `PLAN-adapter-architecture.md` sections on relay.
16
+- [ ] **Session lifecycle from mobile** — List named PAI sessions, see which are running, launch/stop/switch from the phone. "Remote desktop for AI sessions." See AIBroker TODO.
17
+
18
+---
19
+
20
+*Created: 2026-03-05*
app.json
....@@ -5,7 +5,7 @@
55 "version": "1.0.0",
66 "orientation": "portrait",
77 "icon": "./assets/icon.png",
8
- "userInterfaceStyle": "dark",
8
+ "userInterfaceStyle": "automatic",
99 "newArchEnabled": true,
1010 "scheme": "pailot",
1111 "splash": {
....@@ -19,7 +19,8 @@
1919 "appleTeamId": "7KU642K5ZL",
2020 "infoPlist": {
2121 "NSMicrophoneUsageDescription": "PAILot needs microphone access for voice input.",
22
- "NSSpeechRecognitionUsageDescription": "PAILot uses speech recognition to convert your voice to text.",
22
+ "NSPhotoLibraryUsageDescription": "PAILot needs photo library access to send images.",
23
+ "NSCameraUsageDescription": "PAILot needs camera access to take photos.",
2324 "UIBackgroundModes": [
2425 "audio"
2526 ]
....@@ -34,7 +35,9 @@
3435 },
3536 "package": "org.mnsoft.pailot",
3637 "permissions": [
37
- "RECORD_AUDIO"
38
+ "RECORD_AUDIO",
39
+ "READ_MEDIA_IMAGES",
40
+ "CAMERA"
3841 ]
3942 },
4043 "web": {
....@@ -49,14 +52,14 @@
4952 "microphonePermission": "PAILot needs microphone access for voice input."
5053 }
5154 ],
55
+ "expo-secure-store",
5256 [
53
- "expo-speech-recognition",
57
+ "expo-image-picker",
5458 {
55
- "microphonePermission": "PAILot needs microphone access for voice input.",
56
- "speechRecognitionPermission": "PAILot uses speech recognition to convert your voice to text."
59
+ "photosPermission": "PAILot needs photo library access to send images.",
60
+ "cameraPermission": "PAILot needs camera access to take photos."
5761 }
58
- ],
59
- "expo-secure-store"
62
+ ]
6063 ]
6164 }
6265 }
app/_layout.tsx
....@@ -2,21 +2,33 @@
22 import { Stack } from "expo-router";
33 import { ConnectionProvider } from "../contexts/ConnectionContext";
44 import { ChatProvider } from "../contexts/ChatContext";
5
+import { ThemeProvider, useTheme } from "../contexts/ThemeContext";
56 import { StatusBar } from "expo-status-bar";
7
+
8
+function InnerLayout() {
9
+ const { isDark, colors } = useTheme();
10
+ return (
11
+ <>
12
+ <StatusBar style={isDark ? "light" : "dark"} backgroundColor={colors.bg} />
13
+ <Stack
14
+ screenOptions={{
15
+ headerShown: false,
16
+ contentStyle: { backgroundColor: colors.bg },
17
+ animation: "slide_from_right",
18
+ }}
19
+ />
20
+ </>
21
+ );
22
+}
623
724 export default function RootLayout() {
825 return (
9
- <ConnectionProvider>
10
- <ChatProvider>
11
- <StatusBar style="light" backgroundColor="#0A0A0F" />
12
- <Stack
13
- screenOptions={{
14
- headerShown: false,
15
- contentStyle: { backgroundColor: "#0A0A0F" },
16
- animation: "slide_from_right",
17
- }}
18
- />
19
- </ChatProvider>
20
- </ConnectionProvider>
26
+ <ThemeProvider>
27
+ <ConnectionProvider>
28
+ <ChatProvider>
29
+ <InnerLayout />
30
+ </ChatProvider>
31
+ </ConnectionProvider>
32
+ </ThemeProvider>
2133 );
2234 }
app/chat.tsx
....@@ -1,30 +1,42 @@
1
-import React, { useCallback, useState } from "react";
2
-import { Pressable, Text, View } from "react-native";
1
+import React, { useCallback, useEffect, useRef, useState } from "react";
2
+import { ActionSheetIOS, Alert, KeyboardAvoidingView, Platform, Pressable, Text, View } from "react-native";
33 import { SafeAreaView } from "react-native-safe-area-context";
44 import { router } from "expo-router";
55 import { useChat } from "../contexts/ChatContext";
66 import { useConnection } from "../contexts/ConnectionContext";
7
+import { useTheme } from "../contexts/ThemeContext";
78 import { MessageList } from "../components/chat/MessageList";
89 import { InputBar } from "../components/chat/InputBar";
910 import { CommandBar, TextModeCommandBar } from "../components/chat/CommandBar";
11
+import { ImageCaptionModal } from "../components/chat/ImageCaptionModal";
1012 import { StatusDot } from "../components/ui/StatusDot";
11
-import { SessionPicker } from "../components/SessionPicker";
12
-import { playAudio } from "../services/audio";
13
+import { SessionDrawer } from "../components/SessionDrawer";
14
+import { playAudio, stopPlayback, isPlaying, onPlayingChange } from "../services/audio";
15
+
16
+interface StagedImage {
17
+ base64: string;
18
+ uri: string;
19
+ mimeType: string;
20
+}
1321
1422 export default function ChatScreen() {
15
- const { messages, sendTextMessage, clearMessages, requestScreenshot } =
23
+ const { messages, sendTextMessage, sendVoiceMessage, sendImageMessage, clearMessages, requestScreenshot, sessions } =
1624 useChat();
1725 const { status } = useConnection();
26
+ const { colors, mode, cycleMode } = useTheme();
27
+ const themeIcon = mode === "dark" ? "🌙" : mode === "light" ? "☀️" : "📱";
28
+ const activeSessionName = sessions.find((s) => s.isActive)?.name ?? "PAILot";
1829 const [isTextMode, setIsTextMode] = useState(false);
1930 const [showSessions, setShowSessions] = useState(false);
31
+ const [audioPlaying, setAudioPlaying] = useState(false);
32
+ const [stagedImage, setStagedImage] = useState<StagedImage | null>(null);
2033
21
- const handleSessions = useCallback(() => {
22
- setShowSessions(true);
34
+ useEffect(() => {
35
+ return onPlayingChange(setAudioPlaying);
2336 }, []);
2437
2538 const handleScreenshot = useCallback(() => {
2639 requestScreenshot();
27
- router.push("/navigate");
2840 }, [requestScreenshot]);
2941
3042 const handleHelp = useCallback(() => {
....@@ -39,7 +51,90 @@
3951 clearMessages();
4052 }, [clearMessages]);
4153
54
+ // Resolve a picked asset into a StagedImage
55
+ const stageAsset = useCallback(async (asset: { base64?: string | null; uri: string; mimeType?: string | null }) => {
56
+ const mimeType = asset.mimeType ?? (asset.uri.endsWith(".png") ? "image/png" : "image/jpeg");
57
+ let base64 = asset.base64 ?? "";
58
+ if (!base64 && asset.uri) {
59
+ const { readAsStringAsync } = await import("expo-file-system/legacy");
60
+ base64 = await readAsStringAsync(asset.uri, { encoding: "base64" });
61
+ }
62
+ if (base64) {
63
+ setStagedImage({ base64, uri: asset.uri, mimeType });
64
+ }
65
+ }, []);
66
+
67
+ const pickFromLibrary = useCallback(async () => {
68
+ try {
69
+ const ImagePicker = await import("expo-image-picker");
70
+ const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
71
+ if (status !== "granted") {
72
+ Alert.alert("Permission needed", "Please allow photo library access in Settings.");
73
+ return;
74
+ }
75
+ const result = await ImagePicker.launchImageLibraryAsync({
76
+ mediaTypes: ["images"],
77
+ quality: 0.7,
78
+ base64: true,
79
+ });
80
+ if (result.canceled || !result.assets?.[0]) return;
81
+ await stageAsset(result.assets[0]);
82
+ } catch (err: any) {
83
+ Alert.alert("Image Error", err?.message ?? String(err));
84
+ }
85
+ }, [stageAsset]);
86
+
87
+ const pickFromCamera = useCallback(async () => {
88
+ try {
89
+ const ImagePicker = await import("expo-image-picker");
90
+ const { status } = await ImagePicker.requestCameraPermissionsAsync();
91
+ if (status !== "granted") {
92
+ Alert.alert("Permission needed", "Please allow camera access in Settings.");
93
+ return;
94
+ }
95
+ const result = await ImagePicker.launchCameraAsync({
96
+ quality: 0.7,
97
+ base64: true,
98
+ });
99
+ if (result.canceled || !result.assets?.[0]) return;
100
+ await stageAsset(result.assets[0]);
101
+ } catch (err: any) {
102
+ Alert.alert("Camera Error", err?.message ?? String(err));
103
+ }
104
+ }, [stageAsset]);
105
+
106
+ const handlePickImage = useCallback(() => {
107
+ if (Platform.OS === "ios") {
108
+ ActionSheetIOS.showActionSheetWithOptions(
109
+ {
110
+ options: ["Cancel", "Take Photo", "Choose from Library"],
111
+ cancelButtonIndex: 0,
112
+ },
113
+ (index) => {
114
+ if (index === 1) pickFromCamera();
115
+ else if (index === 2) pickFromLibrary();
116
+ },
117
+ );
118
+ } else {
119
+ // Android: just open library (camera is accessible from there)
120
+ pickFromLibrary();
121
+ }
122
+ }, [pickFromCamera, pickFromLibrary]);
123
+
124
+ const handleImageSend = useCallback(
125
+ (caption: string) => {
126
+ if (!stagedImage) return;
127
+ sendImageMessage(stagedImage.base64, caption, stagedImage.mimeType);
128
+ setStagedImage(null);
129
+ },
130
+ [stagedImage, sendImageMessage],
131
+ );
132
+
42133 const handleReplay = useCallback(() => {
134
+ if (isPlaying()) {
135
+ stopPlayback();
136
+ return;
137
+ }
43138 for (let i = messages.length - 1; i >= 0; i--) {
44139 const msg = messages[i];
45140 if (msg.role === "assistant") {
....@@ -52,7 +147,12 @@
52147 }, [messages]);
53148
54149 return (
55
- <SafeAreaView style={{ flex: 1, backgroundColor: "#0A0A0F" }} edges={["top", "bottom"]}>
150
+ <SafeAreaView style={{ flex: 1, backgroundColor: colors.bg }} edges={["top", "bottom"]}>
151
+ <KeyboardAvoidingView
152
+ style={{ flex: 1 }}
153
+ behavior={Platform.OS === "ios" ? "padding" : undefined}
154
+ keyboardVerticalOffset={0}
155
+ >
56156 {/* Header */}
57157 <View
58158 style={{
....@@ -62,37 +162,75 @@
62162 paddingHorizontal: 16,
63163 paddingVertical: 12,
64164 borderBottomWidth: 1,
65
- borderBottomColor: "#2E2E45",
165
+ borderBottomColor: colors.border,
66166 }}
67167 >
68
- <View style={{ flexDirection: "row", alignItems: "center", gap: 10 }}>
69
- <Text
70
- style={{
71
- color: "#E8E8F0",
72
- fontSize: 22,
73
- fontWeight: "800",
74
- letterSpacing: -0.5,
75
- }}
168
+ <View style={{ flexDirection: "row", alignItems: "center", flex: 1, gap: 10 }}>
169
+ <Pressable
170
+ onPress={() => setShowSessions(true)}
171
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
172
+ style={({ pressed }) => ({
173
+ width: 36,
174
+ height: 36,
175
+ alignItems: "center",
176
+ justifyContent: "center",
177
+ borderRadius: 18,
178
+ backgroundColor: pressed ? colors.bgTertiary : colors.bgTertiary + "80",
179
+ })}
76180 >
77
- PAILot
78
- </Text>
79
- <StatusDot status={status} size={8} />
181
+ <Text style={{ color: colors.textSecondary, fontSize: 18 }}>☰</Text>
182
+ </Pressable>
183
+ <Pressable
184
+ onPress={() => setShowSessions(true)}
185
+ style={{ flexDirection: "row", alignItems: "center", gap: 8, flex: 1 }}
186
+ hitSlop={{ top: 6, bottom: 6, left: 0, right: 6 }}
187
+ >
188
+ <Text
189
+ style={{
190
+ color: colors.text,
191
+ fontSize: 22,
192
+ fontWeight: "800",
193
+ letterSpacing: -0.5,
194
+ flexShrink: 1,
195
+ }}
196
+ numberOfLines={1}
197
+ >
198
+ {activeSessionName}
199
+ </Text>
200
+ <StatusDot status={status} size={8} />
201
+ </Pressable>
80202 </View>
81203
82
- <Pressable
83
- onPress={() => router.push("/settings")}
84
- hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }}
85
- style={{
86
- width: 36,
87
- height: 36,
88
- alignItems: "center",
89
- justifyContent: "center",
90
- borderRadius: 18,
91
- backgroundColor: "#1E1E2E",
92
- }}
93
- >
94
- <Text style={{ fontSize: 15 }}>⚙️</Text>
95
- </Pressable>
204
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
205
+ <Pressable
206
+ onPress={cycleMode}
207
+ hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }}
208
+ style={({ pressed }) => ({
209
+ width: 36,
210
+ height: 36,
211
+ alignItems: "center",
212
+ justifyContent: "center",
213
+ borderRadius: 18,
214
+ backgroundColor: pressed ? colors.bgTertiary : colors.bgTertiary + "80",
215
+ })}
216
+ >
217
+ <Text style={{ fontSize: 15 }}>{themeIcon}</Text>
218
+ </Pressable>
219
+ <Pressable
220
+ onPress={() => router.push("/settings")}
221
+ hitSlop={{ top: 6, bottom: 6, left: 6, right: 6 }}
222
+ style={{
223
+ width: 36,
224
+ height: 36,
225
+ alignItems: "center",
226
+ justifyContent: "center",
227
+ borderRadius: 18,
228
+ backgroundColor: colors.bgTertiary,
229
+ }}
230
+ >
231
+ <Text style={{ fontSize: 15 }}>⚙️</Text>
232
+ </Pressable>
233
+ </View>
96234 </View>
97235
98236 {/* Message list */}
....@@ -104,22 +242,22 @@
104242 width: 80,
105243 height: 80,
106244 borderRadius: 40,
107
- backgroundColor: "#1E1E2E",
245
+ backgroundColor: colors.bgTertiary,
108246 alignItems: "center",
109247 justifyContent: "center",
110248 borderWidth: 1,
111
- borderColor: "#2E2E45",
249
+ borderColor: colors.border,
112250 }}
113251 >
114252 <Text style={{ fontSize: 36 }}>🛩</Text>
115253 </View>
116254 <View style={{ alignItems: "center", gap: 6 }}>
117
- <Text style={{ color: "#E8E8F0", fontSize: 20, fontWeight: "700" }}>
255
+ <Text style={{ color: colors.text, fontSize: 20, fontWeight: "700" }}>
118256 PAILot
119257 </Text>
120258 <Text
121259 style={{
122
- color: "#5A5A78",
260
+ color: colors.textMuted,
123261 fontSize: 14,
124262 textAlign: "center",
125263 paddingHorizontal: 40,
....@@ -138,32 +276,46 @@
138276 {/* Command bar */}
139277 {isTextMode ? (
140278 <TextModeCommandBar
141
- onSessions={handleSessions}
142279 onScreenshot={handleScreenshot}
143280 onNavigate={handleNavigate}
281
+ onPhoto={handlePickImage}
282
+ onHelp={handleHelp}
144283 onClear={handleClear}
145284 />
146285 ) : (
147286 <CommandBar
148
- onSessions={handleSessions}
149287 onScreenshot={handleScreenshot}
150
- onHelp={handleHelp}
288
+ onNavigate={handleNavigate}
289
+ onPhoto={handlePickImage}
290
+ onClear={handleClear}
151291 />
152292 )}
153293
154294 {/* Input bar */}
155295 <InputBar
156296 onSendText={sendTextMessage}
297
+ onVoiceRecorded={sendVoiceMessage}
157298 onReplay={handleReplay}
158299 isTextMode={isTextMode}
159300 onToggleMode={() => setIsTextMode((v) => !v)}
301
+ audioPlaying={audioPlaying}
160302 />
161303
162
- {/* Session picker modal */}
163
- <SessionPicker
164
- visible={showSessions}
165
- onClose={() => setShowSessions(false)}
166
- />
304
+ </KeyboardAvoidingView>
305
+
306
+ {/* Image caption modal — WhatsApp-style full-screen preview */}
307
+ <ImageCaptionModal
308
+ visible={!!stagedImage}
309
+ imageUri={stagedImage ? `data:${stagedImage.mimeType};base64,${stagedImage.base64}` : ""}
310
+ onSend={handleImageSend}
311
+ onCancel={() => setStagedImage(null)}
312
+ />
313
+
314
+ {/* Session drawer — absolute overlay outside KAV */}
315
+ <SessionDrawer
316
+ visible={showSessions}
317
+ onClose={() => setShowSessions(false)}
318
+ />
167319 </SafeAreaView>
168320 );
169321 }
app/navigate.tsx
....@@ -9,21 +9,29 @@
99 label: string;
1010 key: string;
1111 icon?: string;
12
- wide?: boolean;
12
+ flex?: number;
1313 }
1414
15
-const NAV_BUTTONS: NavButton[][] = [
15
+const NAV_ROWS: NavButton[][] = [
16
+ // Row 1: Vi motion
1617 [
17
- { label: "Esc", key: "escape" },
18
- { label: "Tab", key: "tab" },
19
- { label: "Enter", key: "enter" },
20
- { label: "Ctrl-C", key: "ctrl-c" },
18
+ { label: "0", key: "0" },
19
+ { label: "k", key: "k", icon: "↑" },
20
+ { label: "G", key: "G" },
21
+ { label: "dd", key: "dd" },
2122 ],
23
+ // Row 2: Arrow keys
2224 [
23
- { label: "", key: "left", icon: "←" },
24
- { label: "", key: "up", icon: "↑" },
25
- { label: "", key: "down", icon: "↓" },
26
- { label: "", key: "right", icon: "→" },
25
+ { label: "h", key: "h", icon: "←" },
26
+ { label: "j", key: "j", icon: "↓" },
27
+ { label: "l", key: "l", icon: "→" },
28
+ { label: "Esc", key: "escape" },
29
+ ],
30
+ // Row 3: Action keys
31
+ [
32
+ { label: "Tab", key: "tab" },
33
+ { label: "Enter", key: "enter", flex: 2 },
34
+ { label: "^C", key: "ctrl-c" },
2735 ],
2836 ];
2937
....@@ -119,18 +127,17 @@
119127 {/* Navigation buttons */}
120128 <View
121129 style={{
122
- paddingHorizontal: 12,
123
- paddingBottom: 8,
124
- gap: 8,
130
+ paddingHorizontal: 16,
131
+ paddingBottom: 12,
132
+ gap: 10,
125133 }}
126134 >
127
- {NAV_BUTTONS.map((row, rowIdx) => (
135
+ {NAV_ROWS.map((row, rowIdx) => (
128136 <View
129137 key={rowIdx}
130138 style={{
131139 flexDirection: "row",
132
- gap: 8,
133
- justifyContent: "center",
140
+ gap: 10,
134141 }}
135142 >
136143 {row.map((btn) => (
....@@ -138,25 +145,30 @@
138145 key={btn.key}
139146 onPress={() => handleNavPress(btn.key)}
140147 style={({ pressed }) => ({
141
- flex: btn.wide ? 2 : 1,
142
- height: 52,
148
+ flex: btn.flex ?? 1,
149
+ height: 56,
143150 borderRadius: 14,
144151 alignItems: "center",
145152 justifyContent: "center",
146153 backgroundColor: pressed ? "#4A9EFF" : "#1E1E2E",
147
- borderWidth: 1,
154
+ borderWidth: 1.5,
148155 borderColor: pressed ? "#4A9EFF" : "#2E2E45",
149156 })}
150157 >
151
- <Text
152
- style={{
153
- color: "#E8E8F0",
154
- fontSize: btn.icon ? 22 : 15,
155
- fontWeight: "700",
156
- }}
157
- >
158
- {btn.icon ?? btn.label}
159
- </Text>
158
+ {btn.icon ? (
159
+ <View style={{ alignItems: "center" }}>
160
+ <Text style={{ color: "#E8E8F0", fontSize: 20, fontWeight: "700" }}>
161
+ {btn.icon}
162
+ </Text>
163
+ <Text style={{ color: "#5A5A78", fontSize: 10, marginTop: 1 }}>
164
+ {btn.label}
165
+ </Text>
166
+ </View>
167
+ ) : (
168
+ <Text style={{ color: "#E8E8F0", fontSize: 16, fontWeight: "700" }}>
169
+ {btn.label}
170
+ </Text>
171
+ )}
160172 </Pressable>
161173 ))}
162174 </View>
app/settings.tsx
....@@ -1,5 +1,6 @@
11 import React, { useCallback, useState } from "react";
22 import {
3
+ Alert,
34 Keyboard,
45 KeyboardAvoidingView,
56 Platform,
....@@ -13,32 +14,47 @@
1314 import { SafeAreaView } from "react-native-safe-area-context";
1415 import { router } from "expo-router";
1516 import { useConnection } from "../contexts/ConnectionContext";
17
+import { useTheme } from "../contexts/ThemeContext";
1618 import { StatusDot } from "../components/ui/StatusDot";
1719 import { ServerConfig } from "../types";
20
+import { sendWol, isValidMac } from "../services/wol";
21
+import { wsClient } from "../services/websocket";
1822
1923 export default function SettingsScreen() {
2024 const { serverConfig, status, connect, disconnect, saveServerConfig } =
2125 useConnection();
26
+ const { colors } = useTheme();
2227
23
- const [host, setHost] = useState(serverConfig?.host ?? "192.168.1.100");
28
+ const [host, setHost] = useState(serverConfig?.host ?? "");
29
+ const [localHost, setLocalHost] = useState(serverConfig?.localHost ?? "");
2430 const [port, setPort] = useState(
2531 serverConfig?.port ? String(serverConfig.port) : "8765"
2632 );
33
+ const [macAddress, setMacAddress] = useState(serverConfig?.macAddress ?? "");
2734 const [saved, setSaved] = useState(false);
35
+ const [waking, setWaking] = useState(false);
2836
2937 const handleSave = useCallback(async () => {
3038 const trimmedHost = host.trim();
3139 const portNum = parseInt(port.trim(), 10);
3240
33
- if (!trimmedHost || isNaN(portNum) || portNum < 1 || portNum > 65535) {
41
+ const trimmedLocal = localHost.trim();
42
+ if ((!trimmedHost && !trimmedLocal) || isNaN(portNum) || portNum < 1 || portNum > 65535) {
3443 return;
3544 }
3645
37
- const config: ServerConfig = { host: trimmedHost, port: portNum };
46
+ const trimmedMac = macAddress.trim();
47
+ const effectiveHost = trimmedHost || trimmedLocal;
48
+ const config: ServerConfig = {
49
+ host: effectiveHost,
50
+ port: portNum,
51
+ ...(trimmedLocal && trimmedHost ? { localHost: trimmedLocal } : {}),
52
+ ...(trimmedMac ? { macAddress: trimmedMac } : {}),
53
+ };
3854 await saveServerConfig(config);
3955 setSaved(true);
4056 setTimeout(() => setSaved(false), 2000);
41
- }, [host, port, saveServerConfig]);
57
+ }, [host, localHost, port, macAddress, saveServerConfig]);
4258
4359 const handleConnect = useCallback(() => {
4460 if (status === "connected" || status === "connecting") {
....@@ -48,17 +64,34 @@
4864 }
4965 }, [status, connect, disconnect]);
5066
51
- const isFormValid = host.trim().length > 0 && parseInt(port, 10) > 0;
67
+ const handleWake = useCallback(async () => {
68
+ const mac = macAddress.trim() || serverConfig?.macAddress;
69
+ if (!mac || !isValidMac(mac)) {
70
+ Alert.alert("Invalid MAC", "Enter a valid MAC address (e.g. 6a:8a:e7:b3:8e:5c)");
71
+ return;
72
+ }
73
+ setWaking(true);
74
+ try {
75
+ await sendWol(mac, host.trim() || serverConfig?.host);
76
+ Alert.alert("WoL Sent", "Magic packet sent. The Mac should wake in a few seconds.");
77
+ } catch (err) {
78
+ Alert.alert("WoL Failed", err instanceof Error ? err.message : String(err));
79
+ } finally {
80
+ setWaking(false);
81
+ }
82
+ }, [macAddress, host, serverConfig]);
83
+
84
+ const isFormValid = (host.trim().length > 0 || localHost.trim().length > 0) && parseInt(port, 10) > 0;
5285
5386 return (
54
- <SafeAreaView className="flex-1 bg-pai-bg" edges={["top", "bottom"]}>
87
+ <SafeAreaView style={{ flex: 1, backgroundColor: colors.bg }} edges={["top", "bottom"]}>
5588 <KeyboardAvoidingView
56
- className="flex-1"
89
+ style={{ flex: 1 }}
5790 behavior={Platform.OS === "ios" ? "padding" : "height"}
5891 >
5992 <TouchableWithoutFeedback onPress={Keyboard.dismiss}>
6093 <ScrollView
61
- className="flex-1"
94
+ style={{ flex: 1 }}
6295 contentContainerStyle={{ paddingBottom: 32 }}
6396 keyboardShouldPersistTaps="handled"
6497 >
....@@ -70,7 +103,7 @@
70103 paddingHorizontal: 16,
71104 paddingVertical: 12,
72105 borderBottomWidth: 1,
73
- borderBottomColor: "#2E2E45",
106
+ borderBottomColor: colors.border,
74107 }}
75108 >
76109 <Pressable
....@@ -82,26 +115,42 @@
82115 alignItems: "center",
83116 justifyContent: "center",
84117 borderRadius: 18,
85
- backgroundColor: "#1E1E2E",
118
+ backgroundColor: colors.bgTertiary,
86119 marginRight: 12,
87120 }}
88121 >
89
- <Text style={{ color: "#E8E8F0", fontSize: 16 }}>←</Text>
122
+ <Text style={{ color: colors.text, fontSize: 16 }}>{"\u2190"}</Text>
90123 </Pressable>
91
- <Text style={{ color: "#E8E8F0", fontSize: 22, fontWeight: "800", letterSpacing: -0.5 }}>
124
+ <Text style={{ color: colors.text, fontSize: 22, fontWeight: "800", letterSpacing: -0.5 }}>
92125 Settings
93126 </Text>
94127 </View>
95128
96
- <View className="px-4 mt-6">
129
+ <View style={{ paddingHorizontal: 16, marginTop: 24 }}>
97130 {/* Connection status card */}
98
- <View className="bg-pai-surface rounded-2xl p-4 mb-6">
99
- <Text className="text-pai-text-secondary text-xs font-medium uppercase tracking-widest mb-3">
131
+ <View
132
+ style={{
133
+ backgroundColor: colors.bgTertiary,
134
+ borderRadius: 16,
135
+ padding: 16,
136
+ marginBottom: 24,
137
+ }}
138
+ >
139
+ <Text
140
+ style={{
141
+ color: colors.textSecondary,
142
+ fontSize: 11,
143
+ fontWeight: "500",
144
+ textTransform: "uppercase",
145
+ letterSpacing: 1.5,
146
+ marginBottom: 12,
147
+ }}
148
+ >
100149 Connection Status
101150 </Text>
102
- <View className="flex-row items-center gap-3">
151
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 12 }}>
103152 <StatusDot status={status} size={12} />
104
- <Text className="text-pai-text text-base font-medium">
153
+ <Text style={{ color: colors.text, fontSize: 16, fontWeight: "500" }}>
105154 {status === "connected"
106155 ? "Connected"
107156 : status === "connecting"
....@@ -110,47 +159,117 @@
110159 </Text>
111160 </View>
112161 {serverConfig && (
113
- <Text className="text-pai-text-muted text-sm mt-2">
114
- ws://{serverConfig.host}:{serverConfig.port}
162
+ <Text style={{ color: colors.textMuted, fontSize: 14, marginTop: 8 }}>
163
+ {wsClient.currentUrl || `ws://${serverConfig.host}:${serverConfig.port}`}
115164 </Text>
116165 )}
117166 </View>
118167
119168 {/* Server config */}
120
- <Text className="text-pai-text-secondary text-xs font-medium uppercase tracking-widest mb-3">
169
+ <Text
170
+ style={{
171
+ color: colors.textSecondary,
172
+ fontSize: 11,
173
+ fontWeight: "500",
174
+ textTransform: "uppercase",
175
+ letterSpacing: 1.5,
176
+ marginBottom: 12,
177
+ }}
178
+ >
121179 Server Configuration
122180 </Text>
123181
124
- <View className="bg-pai-surface rounded-2xl overflow-hidden mb-4">
125
- {/* Host */}
126
- <View className="px-4 py-3 border-b border-pai-border">
127
- <Text className="text-pai-text-muted text-xs mb-1">
128
- Host / IP Address
182
+ <View
183
+ style={{
184
+ backgroundColor: colors.bgTertiary,
185
+ borderRadius: 16,
186
+ overflow: "hidden",
187
+ marginBottom: 16,
188
+ }}
189
+ >
190
+ {/* Local Host (preferred when on same network) */}
191
+ <View
192
+ style={{
193
+ paddingHorizontal: 16,
194
+ paddingVertical: 12,
195
+ borderBottomWidth: 1,
196
+ borderBottomColor: colors.border,
197
+ }}
198
+ >
199
+ <Text style={{ color: colors.textMuted, fontSize: 11, marginBottom: 4 }}>
200
+ Local Address (optional)
201
+ </Text>
202
+ <TextInput
203
+ value={localHost}
204
+ onChangeText={setLocalHost}
205
+ placeholder="192.168.1.100"
206
+ placeholderTextColor={colors.textMuted}
207
+ autoCapitalize="none"
208
+ autoCorrect={false}
209
+ keyboardType="url"
210
+ style={{ color: colors.text, fontSize: 16, padding: 0 }}
211
+ />
212
+ </View>
213
+
214
+ {/* Remote Host (fallback / external) */}
215
+ <View
216
+ style={{
217
+ paddingHorizontal: 16,
218
+ paddingVertical: 12,
219
+ borderBottomWidth: 1,
220
+ borderBottomColor: colors.border,
221
+ }}
222
+ >
223
+ <Text style={{ color: colors.textMuted, fontSize: 11, marginBottom: 4 }}>
224
+ Remote Address
129225 </Text>
130226 <TextInput
131227 value={host}
132228 onChangeText={setHost}
133
- placeholder="192.168.1.100"
134
- placeholderTextColor="#5A5A78"
229
+ placeholder="myhost.example.com"
230
+ placeholderTextColor={colors.textMuted}
135231 autoCapitalize="none"
136232 autoCorrect={false}
137233 keyboardType="url"
138
- style={{ color: "#E8E8F0", fontSize: 16, padding: 0 }}
234
+ style={{ color: colors.text, fontSize: 16, padding: 0 }}
139235 />
140236 </View>
141237
142238 {/* Port */}
143
- <View className="px-4 py-3">
144
- <Text className="text-pai-text-muted text-xs mb-1">
239
+ <View
240
+ style={{
241
+ paddingHorizontal: 16,
242
+ paddingVertical: 12,
243
+ borderBottomWidth: 1,
244
+ borderBottomColor: colors.border,
245
+ }}
246
+ >
247
+ <Text style={{ color: colors.textMuted, fontSize: 11, marginBottom: 4 }}>
145248 Port
146249 </Text>
147250 <TextInput
148251 value={port}
149252 onChangeText={setPort}
150253 placeholder="8765"
151
- placeholderTextColor="#5A5A78"
254
+ placeholderTextColor={colors.textMuted}
152255 keyboardType="number-pad"
153
- style={{ color: "#E8E8F0", fontSize: 16, padding: 0 }}
256
+ style={{ color: colors.text, fontSize: 16, padding: 0 }}
257
+ />
258
+ </View>
259
+
260
+ {/* MAC Address for Wake-on-LAN */}
261
+ <View style={{ paddingHorizontal: 16, paddingVertical: 12 }}>
262
+ <Text style={{ color: colors.textMuted, fontSize: 11, marginBottom: 4 }}>
263
+ MAC Address (Wake-on-LAN)
264
+ </Text>
265
+ <TextInput
266
+ value={macAddress}
267
+ onChangeText={setMacAddress}
268
+ placeholder="6a:8a:e7:b3:8e:5c"
269
+ placeholderTextColor={colors.textMuted}
270
+ autoCapitalize="none"
271
+ autoCorrect={false}
272
+ style={{ color: colors.text, fontSize: 16, padding: 0 }}
154273 />
155274 </View>
156275 </View>
....@@ -159,16 +278,51 @@
159278 <Pressable
160279 onPress={handleSave}
161280 disabled={!isFormValid}
162
- className={`rounded-2xl py-4 items-center mb-3 ${
163
- isFormValid ? "bg-pai-accent" : "bg-pai-surface"
164
- }`}
281
+ style={{
282
+ borderRadius: 16,
283
+ paddingVertical: 16,
284
+ alignItems: "center",
285
+ marginBottom: 12,
286
+ backgroundColor: isFormValid ? colors.accent : colors.bgTertiary,
287
+ }}
165288 >
166289 <Text
167
- className={`text-base font-semibold ${
168
- isFormValid ? "text-white" : "text-pai-text-muted"
169
- }`}
290
+ style={{
291
+ fontSize: 16,
292
+ fontWeight: "600",
293
+ color: isFormValid ? "#FFF" : colors.textMuted,
294
+ }}
170295 >
171296 {saved ? "Saved!" : "Save Configuration"}
297
+ </Text>
298
+ </Pressable>
299
+
300
+ {/* Wake-on-LAN button */}
301
+ <Pressable
302
+ onPress={handleWake}
303
+ disabled={waking || (!macAddress.trim() && !serverConfig?.macAddress)}
304
+ style={{
305
+ borderRadius: 16,
306
+ paddingVertical: 16,
307
+ alignItems: "center",
308
+ marginBottom: 12,
309
+ backgroundColor:
310
+ macAddress.trim() || serverConfig?.macAddress
311
+ ? "#FF950033"
312
+ : colors.bgTertiary,
313
+ }}
314
+ >
315
+ <Text
316
+ style={{
317
+ fontSize: 16,
318
+ fontWeight: "600",
319
+ color:
320
+ macAddress.trim() || serverConfig?.macAddress
321
+ ? "#FF9500"
322
+ : colors.textMuted,
323
+ }}
324
+ >
325
+ {waking ? "Sending..." : "Wake Mac (WoL)"}
172326 </Text>
173327 </Pressable>
174328
....@@ -176,18 +330,22 @@
176330 <Pressable
177331 onPress={handleConnect}
178332 disabled={!serverConfig}
179
- className={`rounded-2xl py-4 items-center ${
180
- status === "connected"
181
- ? "bg-pai-error/20"
182
- : "bg-pai-success/20"
183
- }`}
333
+ style={{
334
+ borderRadius: 16,
335
+ paddingVertical: 16,
336
+ alignItems: "center",
337
+ backgroundColor:
338
+ status === "connected"
339
+ ? colors.danger + "33"
340
+ : "#2ED57333",
341
+ }}
184342 >
185343 <Text
186
- className={`text-base font-semibold ${
187
- status === "connected"
188
- ? "text-pai-error"
189
- : "text-pai-success"
190
- }`}
344
+ style={{
345
+ fontSize: 16,
346
+ fontWeight: "600",
347
+ color: status === "connected" ? colors.danger : "#2ED573",
348
+ }}
191349 >
192350 {status === "connected"
193351 ? "Disconnect"
assets/android-icon-background.png
Binary files differ
assets/android-icon-foreground.png
Binary files differ
assets/android-icon-monochrome.png
Binary files differ
assets/favicon.png
Binary files differ
assets/icon.png
Binary files differ
assets/splash-icon.png
Binary files differ
bun.lock
....@@ -0,0 +1,1664 @@
1
+{
2
+ "lockfileVersion": 1,
3
+ "configVersion": 0,
4
+ "workspaces": {
5
+ "": {
6
+ "name": "pailot",
7
+ "dependencies": {
8
+ "@react-navigation/bottom-tabs": "^7.15.3",
9
+ "@react-navigation/native": "^7.1.31",
10
+ "expo": "~55.0.4",
11
+ "expo-audio": "^55.0.8",
12
+ "expo-constants": "~55.0.7",
13
+ "expo-file-system": "~55.0.10",
14
+ "expo-haptics": "~55.0.8",
15
+ "expo-image-picker": "~55.0.11",
16
+ "expo-linking": "~55.0.7",
17
+ "expo-router": "~55.0.3",
18
+ "expo-secure-store": "~55.0.8",
19
+ "expo-sharing": "~55.0.11",
20
+ "expo-splash-screen": "~55.0.10",
21
+ "expo-status-bar": "~55.0.4",
22
+ "expo-system-ui": "~55.0.9",
23
+ "expo-web-browser": "~55.0.9",
24
+ "nativewind": "^4",
25
+ "react": "19.2.0",
26
+ "react-dom": "^19.2.4",
27
+ "react-native": "0.83.2",
28
+ "react-native-draggable-flatlist": "^4.0.3",
29
+ "react-native-gesture-handler": "~2.30.0",
30
+ "react-native-reanimated": "4.2.1",
31
+ "react-native-safe-area-context": "~5.6.2",
32
+ "react-native-screens": "~4.23.0",
33
+ "react-native-svg": "15.15.3",
34
+ "react-native-udp": "^4.1.7",
35
+ "react-native-web": "^0.21.0",
36
+ "react-native-worklets": "0.7.2",
37
+ },
38
+ "devDependencies": {
39
+ "@types/react": "~19.2.2",
40
+ "babel-plugin-module-resolver": "^5.0.2",
41
+ "babel-preset-expo": "^55.0.10",
42
+ "tailwindcss": "^3.4.19",
43
+ "typescript": "~5.9.2",
44
+ },
45
+ },
46
+ },
47
+ "packages": {
48
+ "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
49
+
50
+ "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
51
+
52
+ "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
53
+
54
+ "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
55
+
56
+ "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
57
+
58
+ "@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.27.3", "", { "dependencies": { "@babel/types": "^7.27.3" } }, "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg=="],
59
+
60
+ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
61
+
62
+ "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="],
63
+
64
+ "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="],
65
+
66
+ "@babel/helper-define-polyfill-provider": ["@babel/helper-define-polyfill-provider@0.6.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "debug": "^4.4.3", "lodash.debounce": "^4.0.8", "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-mOAsxeeKkUKayvZR3HeTYD/fICpCPLJrU5ZjelT/PA6WHtNDBOE436YiaEUvHN454bRM3CebhDsIpieCc4texA=="],
67
+
68
+ "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="],
69
+
70
+ "@babel/helper-member-expression-to-functions": ["@babel/helper-member-expression-to-functions@7.28.5", "", { "dependencies": { "@babel/traverse": "^7.28.5", "@babel/types": "^7.28.5" } }, "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg=="],
71
+
72
+ "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="],
73
+
74
+ "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="],
75
+
76
+ "@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="],
77
+
78
+ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="],
79
+
80
+ "@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="],
81
+
82
+ "@babel/helper-replace-supers": ["@babel/helper-replace-supers@7.28.6", "", { "dependencies": { "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg=="],
83
+
84
+ "@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg=="],
85
+
86
+ "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
87
+
88
+ "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
89
+
90
+ "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
91
+
92
+ "@babel/helper-wrap-function": ["@babel/helper-wrap-function@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ=="],
93
+
94
+ "@babel/helpers": ["@babel/helpers@7.28.6", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw=="],
95
+
96
+ "@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
97
+
98
+ "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="],
99
+
100
+ "@babel/plugin-proposal-export-default-from": ["@babel/plugin-proposal-export-default-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw=="],
101
+
102
+ "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="],
103
+
104
+ "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="],
105
+
106
+ "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="],
107
+
108
+ "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="],
109
+
110
+ "@babel/plugin-syntax-decorators": ["@babel/plugin-syntax-decorators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA=="],
111
+
112
+ "@babel/plugin-syntax-dynamic-import": ["@babel/plugin-syntax-dynamic-import@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ=="],
113
+
114
+ "@babel/plugin-syntax-export-default-from": ["@babel/plugin-syntax-export-default-from@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ=="],
115
+
116
+ "@babel/plugin-syntax-flow": ["@babel/plugin-syntax-flow@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew=="],
117
+
118
+ "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw=="],
119
+
120
+ "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="],
121
+
122
+ "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="],
123
+
124
+ "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w=="],
125
+
126
+ "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="],
127
+
128
+ "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="],
129
+
130
+ "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="],
131
+
132
+ "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="],
133
+
134
+ "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="],
135
+
136
+ "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="],
137
+
138
+ "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="],
139
+
140
+ "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="],
141
+
142
+ "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A=="],
143
+
144
+ "@babel/plugin-transform-arrow-functions": ["@babel/plugin-transform-arrow-functions@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA=="],
145
+
146
+ "@babel/plugin-transform-async-generator-functions": ["@babel/plugin-transform-async-generator-functions@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", "@babel/traverse": "^7.29.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w=="],
147
+
148
+ "@babel/plugin-transform-async-to-generator": ["@babel/plugin-transform-async-to-generator@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g=="],
149
+
150
+ "@babel/plugin-transform-block-scoping": ["@babel/plugin-transform-block-scoping@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw=="],
151
+
152
+ "@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.27.1", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA=="],
153
+
154
+ "@babel/plugin-transform-class-static-block": ["@babel/plugin-transform-class-static-block@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.12.0" } }, "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ=="],
155
+
156
+ "@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.4", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/traverse": "^7.28.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA=="],
157
+
158
+ "@babel/plugin-transform-computed-properties": ["@babel/plugin-transform-computed-properties@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/template": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ=="],
159
+
160
+ "@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="],
161
+
162
+ "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="],
163
+
164
+ "@babel/plugin-transform-flow-strip-types": ["@babel/plugin-transform-flow-strip-types@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-flow": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg=="],
165
+
166
+ "@babel/plugin-transform-for-of": ["@babel/plugin-transform-for-of@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw=="],
167
+
168
+ "@babel/plugin-transform-function-name": ["@babel/plugin-transform-function-name@7.27.1", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ=="],
169
+
170
+ "@babel/plugin-transform-literals": ["@babel/plugin-transform-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA=="],
171
+
172
+ "@babel/plugin-transform-logical-assignment-operators": ["@babel/plugin-transform-logical-assignment-operators@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A=="],
173
+
174
+ "@babel/plugin-transform-modules-commonjs": ["@babel/plugin-transform-modules-commonjs@7.28.6", "", { "dependencies": { "@babel/helper-module-transforms": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA=="],
175
+
176
+ "@babel/plugin-transform-named-capturing-groups-regex": ["@babel/plugin-transform-named-capturing-groups-regex@7.29.0", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ=="],
177
+
178
+ "@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA=="],
179
+
180
+ "@babel/plugin-transform-numeric-separator": ["@babel/plugin-transform-numeric-separator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w=="],
181
+
182
+ "@babel/plugin-transform-object-rest-spread": ["@babel/plugin-transform-object-rest-spread@7.28.6", "", { "dependencies": { "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA=="],
183
+
184
+ "@babel/plugin-transform-optional-catch-binding": ["@babel/plugin-transform-optional-catch-binding@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ=="],
185
+
186
+ "@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg=="],
187
+
188
+ "@babel/plugin-transform-parameters": ["@babel/plugin-transform-parameters@7.27.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg=="],
189
+
190
+ "@babel/plugin-transform-private-methods": ["@babel/plugin-transform-private-methods@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg=="],
191
+
192
+ "@babel/plugin-transform-private-property-in-object": ["@babel/plugin-transform-private-property-in-object@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA=="],
193
+
194
+ "@babel/plugin-transform-react-display-name": ["@babel/plugin-transform-react-display-name@7.28.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA=="],
195
+
196
+ "@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-jsx": "^7.28.6", "@babel/types": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow=="],
197
+
198
+ "@babel/plugin-transform-react-jsx-development": ["@babel/plugin-transform-react-jsx-development@7.27.1", "", { "dependencies": { "@babel/plugin-transform-react-jsx": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q=="],
199
+
200
+ "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="],
201
+
202
+ "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="],
203
+
204
+ "@babel/plugin-transform-react-pure-annotations": ["@babel/plugin-transform-react-pure-annotations@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA=="],
205
+
206
+ "@babel/plugin-transform-regenerator": ["@babel/plugin-transform-regenerator@7.29.0", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog=="],
207
+
208
+ "@babel/plugin-transform-runtime": ["@babel/plugin-transform-runtime@7.29.0", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "babel-plugin-polyfill-corejs2": "^0.4.14", "babel-plugin-polyfill-corejs3": "^0.13.0", "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w=="],
209
+
210
+ "@babel/plugin-transform-shorthand-properties": ["@babel/plugin-transform-shorthand-properties@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ=="],
211
+
212
+ "@babel/plugin-transform-spread": ["@babel/plugin-transform-spread@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA=="],
213
+
214
+ "@babel/plugin-transform-sticky-regex": ["@babel/plugin-transform-sticky-regex@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g=="],
215
+
216
+ "@babel/plugin-transform-template-literals": ["@babel/plugin-transform-template-literals@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg=="],
217
+
218
+ "@babel/plugin-transform-typescript": ["@babel/plugin-transform-typescript@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw=="],
219
+
220
+ "@babel/plugin-transform-unicode-regex": ["@babel/plugin-transform-unicode-regex@7.27.1", "", { "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw=="],
221
+
222
+ "@babel/preset-react": ["@babel/preset-react@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-transform-react-display-name": "^7.28.0", "@babel/plugin-transform-react-jsx": "^7.27.1", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ=="],
223
+
224
+ "@babel/preset-typescript": ["@babel/preset-typescript@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g=="],
225
+
226
+ "@babel/runtime": ["@babel/runtime@7.28.6", "", {}, "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA=="],
227
+
228
+ "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="],
229
+
230
+ "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
231
+
232
+ "@babel/traverse--for-generate-function-map": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
233
+
234
+ "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
235
+
236
+ "@egjs/hammerjs": ["@egjs/hammerjs@2.0.17", "", { "dependencies": { "@types/hammerjs": "^2.0.36" } }, "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A=="],
237
+
238
+ "@expo-google-fonts/material-symbols": ["@expo-google-fonts/material-symbols@0.4.24", "", {}, "sha512-1bJ63Yv2Bn8SN2MjrlbwLwUhnC8COOeejd15H88WjCtw5iNErqEPaBnpvmYyqciVYwudGo5drUIdY9C/5yPGbg=="],
239
+
240
+ "@expo/cli": ["@expo/cli@55.0.14", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~55.0.8", "@expo/config-plugins": "~55.0.6", "@expo/devcert": "^1.2.1", "@expo/env": "~2.1.1", "@expo/image-utils": "^0.8.12", "@expo/json-file": "^10.0.12", "@expo/log-box": "55.0.7", "@expo/metro": "~54.2.0", "@expo/metro-config": "~55.0.9", "@expo/osascript": "^2.4.2", "@expo/package-manager": "^1.10.3", "@expo/plist": "^0.5.2", "@expo/prebuild-config": "^55.0.8", "@expo/require-utils": "^55.0.2", "@expo/router-server": "^55.0.9", "@expo/schema-utils": "^55.0.2", "@expo/spawn-async": "^1.7.2", "@expo/ws-tunnel": "^1.0.1", "@expo/xcpretty": "^4.4.0", "@react-native/dev-middleware": "0.83.2", "accepts": "^1.3.8", "arg": "^5.0.2", "better-opn": "~3.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.3", "expo-server": "^55.0.6", "fetch-nodeshim": "^0.4.6", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.0", "multitars": "^0.2.3", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.3", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "source-map-support": "~0.5.21", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "bin": { "expo-internal": "build/bin/cli" } }, "sha512-glXPSjjLCIz+KX/ezqLTGIF9eTE1lexiCxunvB3loRZNnGeBDGW3eF++cuPKudW26jeC6bqZkcqBG7Lp0Sp9qg=="],
241
+
242
+ "@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="],
243
+
244
+ "@expo/config": ["@expo/config@55.0.8", "", { "dependencies": { "@expo/config-plugins": "~55.0.6", "@expo/config-types": "^55.0.5", "@expo/json-file": "^10.0.12", "@expo/require-utils": "^55.0.2", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-from": "^5.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4" } }, "sha512-D7RYYHfErCgEllGxNwdYdkgzLna7zkzUECBV3snbUpf7RvIpB5l1LpCgzuVoc5KVew5h7N1Tn4LnT/tBSUZsQg=="],
245
+
246
+ "@expo/config-plugins": ["@expo/config-plugins@55.0.6", "", { "dependencies": { "@expo/config-types": "^55.0.5", "@expo/json-file": "~10.0.12", "@expo/plist": "^0.5.2", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-from": "^5.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-cIox6FjZlFaaX40rbQ3DvP9e87S5X85H9uw+BAxJE5timkMhuByy3GAlOsj1h96EyzSiol7Q6YIGgY1Jiz4M+A=="],
247
+
248
+ "@expo/config-types": ["@expo/config-types@55.0.5", "", {}, "sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg=="],
249
+
250
+ "@expo/devcert": ["@expo/devcert@1.2.1", "", { "dependencies": { "@expo/sudo-prompt": "^9.3.1", "debug": "^3.1.0" } }, "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA=="],
251
+
252
+ "@expo/devtools": ["@expo/devtools@55.0.2", "", { "dependencies": { "chalk": "^4.1.2" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4VsFn9MUriocyuhyA+ycJP3TJhUsOFHDc270l9h3LhNpXMf6wvIdGcA0QzXkZtORXmlDybWXRP2KT1k36HcQkA=="],
253
+
254
+ "@expo/dom-webview": ["@expo/dom-webview@55.0.3", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-bY4/rfcZ0f43DvOtMn8/kmPlmo01tex5hRoc5hKbwBwQjqWQuQt0ACwu7akR9IHI4j0WNG48eL6cZB6dZUFrzg=="],
255
+
256
+ "@expo/env": ["@expo/env@2.1.1", "", { "dependencies": { "chalk": "^4.0.0", "debug": "^4.3.4", "getenv": "^2.0.0" } }, "sha512-rVvHC4I6xlPcg+mAO09ydUi2Wjv1ZytpLmHOSzvXzBAz9mMrJggqCe4s4dubjJvi/Ino/xQCLhbaLCnTtLpikg=="],
257
+
258
+ "@expo/fingerprint": ["@expo/fingerprint@0.16.5", "", { "dependencies": { "@expo/env": "^2.0.11", "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "minimatch": "^10.2.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-mLrcymtgkW9IJ/G1e8MH1Xt2VIb1MOS86ePY0ePcnV3nVyJqm7gfa/AXD1Hk+eZXvf8XhioYz6QZaamBdEzR3A=="],
259
+
260
+ "@expo/image-utils": ["@expo/image-utils@0.8.12", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0" } }, "sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A=="],
261
+
262
+ "@expo/json-file": ["@expo/json-file@10.0.12", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-inbDycp1rMAelAofg7h/mMzIe+Owx6F7pur3XdQ3EPTy00tme+4P6FWgHKUcjN8dBSrnbRNpSyh5/shzHyVCyQ=="],
263
+
264
+ "@expo/local-build-cache-provider": ["@expo/local-build-cache-provider@55.0.6", "", { "dependencies": { "@expo/config": "~55.0.8", "chalk": "^4.1.2" } }, "sha512-4kfdv48sKzokijMqi07fINYA9/XprshmPgSLf8i69XgzIv2YdRyBbb70SzrufB7PDneFoltz8N83icW8gOOj1g=="],
265
+
266
+ "@expo/log-box": ["@expo/log-box@55.0.7", "", { "dependencies": { "@expo/dom-webview": "^55.0.3", "anser": "^1.4.9", "stacktrace-parser": "^0.1.10" }, "peerDependencies": { "@expo/dom-webview": "^55.0.3", "expo": "*", "react": "*", "react-native": "*" } }, "sha512-m7V1k2vlMp4NOj3fopjOg4zl/ANXyTRF3HMTMep2GZAKsPiDzgOQ41nm8CaU50/HlDIGXlCObss07gOn20UpHQ=="],
267
+
268
+ "@expo/metro": ["@expo/metro@54.2.0", "", { "dependencies": { "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-minify-terser": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3" } }, "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w=="],
269
+
270
+ "@expo/metro-config": ["@expo/metro-config@55.0.9", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.20.0", "@babel/generator": "^7.20.5", "@expo/config": "~55.0.8", "@expo/env": "~2.1.1", "@expo/json-file": "~10.0.12", "@expo/metro": "~54.2.0", "@expo/spawn-async": "^1.7.2", "browserslist": "^4.25.0", "chalk": "^4.1.0", "debug": "^4.3.2", "getenv": "^2.0.0", "glob": "^13.0.0", "hermes-parser": "^0.32.0", "jsc-safe-url": "^0.2.4", "lightningcss": "^1.30.1", "picomatch": "^4.0.3", "postcss": "~8.4.32", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-ZJFEfat/+dLUhFyFFWrzMjAqAwwUaJ3RD42QNqR7jh+RVYkAf6XYLynb5qrKJTHI1EcOx4KoO1717yXYYRFDBA=="],
271
+
272
+ "@expo/metro-runtime": ["@expo/metro-runtime@55.0.6", "", { "dependencies": { "@expo/log-box": "55.0.7", "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" } }, "sha512-l8VvgKN9md+URjeQDB+DnHVmvpcWI6zFLH6yv7GTv4sfRDKyaZ5zDXYjTP1phYdgW6ea2NrRtCGNIxylWhsgtg=="],
273
+
274
+ "@expo/osascript": ["@expo/osascript@2.4.2", "", { "dependencies": { "@expo/spawn-async": "^1.7.2" } }, "sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw=="],
275
+
276
+ "@expo/package-manager": ["@expo/package-manager@1.10.3", "", { "dependencies": { "@expo/json-file": "^10.0.12", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-ZuXiK/9fCrIuLjPSe1VYmfp0Sa85kCMwd8QQpgyi5ufppYKRtLBg14QOgUqj8ZMbJTxE0xqzd0XR7kOs3vAK9A=="],
277
+
278
+ "@expo/plist": ["@expo/plist@0.5.2", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g=="],
279
+
280
+ "@expo/prebuild-config": ["@expo/prebuild-config@55.0.8", "", { "dependencies": { "@expo/config": "~55.0.8", "@expo/config-plugins": "~55.0.6", "@expo/config-types": "^55.0.5", "@expo/image-utils": "^0.8.12", "@expo/json-file": "^10.0.12", "@react-native/normalize-colors": "0.83.2", "debug": "^4.3.1", "resolve-from": "^5.0.0", "semver": "^7.6.0", "xml2js": "0.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-VJNJiOmmZgyDnR7JMmc3B8Z0ZepZ17I8Wtw+wAH/2+UCUsFg588XU+bwgYcFGw+is28kwGjY46z43kfufpxOnA=="],
281
+
282
+ "@expo/require-utils": ["@expo/require-utils@55.0.2", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0" } }, "sha512-dV5oCShQ1umKBKagMMT4B/N+SREsQe3lU4Zgmko5AO0rxKV0tynZT6xXs+e2JxuqT4Rz997atg7pki0BnZb4uw=="],
283
+
284
+ "@expo/router-server": ["@expo/router-server@55.0.9", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@expo/metro-runtime": "^55.0.6", "expo": "*", "expo-constants": "^55.0.7", "expo-font": "^55.0.4", "expo-router": "*", "expo-server": "^55.0.6", "react": "*", "react-dom": "*", "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" }, "optionalPeers": ["@expo/metro-runtime", "react-server-dom-webpack"] }, "sha512-LcCFi+P1qfZOsw0DO4JwNKRxtWt4u2bjTYj0PUe4WVf9NVG/NfUetAXYRbBS6P+gupfM6SC+/bdzdqCWQh7j8g=="],
285
+
286
+ "@expo/schema-utils": ["@expo/schema-utils@55.0.2", "", {}, "sha512-QZ5WKbJOWkCrMq0/kfhV9ry8te/OaS34YgLVpG8u9y2gix96TlpRTbxM/YATjNcUR2s4fiQmPCOxkGtog4i37g=="],
287
+
288
+ "@expo/sdk-runtime-versions": ["@expo/sdk-runtime-versions@1.0.0", "", {}, "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ=="],
289
+
290
+ "@expo/spawn-async": ["@expo/spawn-async@1.7.2", "", { "dependencies": { "cross-spawn": "^7.0.3" } }, "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew=="],
291
+
292
+ "@expo/sudo-prompt": ["@expo/sudo-prompt@9.3.2", "", {}, "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw=="],
293
+
294
+ "@expo/vector-icons": ["@expo/vector-icons@15.1.1", "", { "peerDependencies": { "expo-font": ">=14.0.4", "react": "*", "react-native": "*" } }, "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw=="],
295
+
296
+ "@expo/ws-tunnel": ["@expo/ws-tunnel@1.0.6", "", {}, "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q=="],
297
+
298
+ "@expo/xcpretty": ["@expo/xcpretty@4.4.1", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, "sha512-KZNxZvnGCtiM2aYYZ6Wz0Ix5r47dAvpNLApFtZWnSoERzAdOMzVBOPysBoM0JlF6FKWZ8GPqgn6qt3dV/8Zlpg=="],
299
+
300
+ "@isaacs/ttlcache": ["@isaacs/ttlcache@1.4.1", "", {}, "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA=="],
301
+
302
+ "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="],
303
+
304
+ "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
305
+
306
+ "@jest/create-cache-key-function": ["@jest/create-cache-key-function@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3" } }, "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA=="],
307
+
308
+ "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="],
309
+
310
+ "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="],
311
+
312
+ "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
313
+
314
+ "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="],
315
+
316
+ "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
317
+
318
+ "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
319
+
320
+ "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
321
+
322
+ "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
323
+
324
+ "@jridgewell/source-map": ["@jridgewell/source-map@0.3.11", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" } }, "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA=="],
325
+
326
+ "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
327
+
328
+ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
329
+
330
+ "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="],
331
+
332
+ "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="],
333
+
334
+ "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="],
335
+
336
+ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.3", "", {}, "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg=="],
337
+
338
+ "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="],
339
+
340
+ "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="],
341
+
342
+ "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="],
343
+
344
+ "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw=="],
345
+
346
+ "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="],
347
+
348
+ "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg=="],
349
+
350
+ "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw=="],
351
+
352
+ "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="],
353
+
354
+ "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="],
355
+
356
+ "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="],
357
+
358
+ "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.5", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ=="],
359
+
360
+ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="],
361
+
362
+ "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA=="],
363
+
364
+ "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA=="],
365
+
366
+ "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.3", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.5", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.11", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react-dom"] }, "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A=="],
367
+
368
+ "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="],
369
+
370
+ "@radix-ui/react-use-controllable-state": ["@radix-ui/react-use-controllable-state@1.2.2", "", { "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg=="],
371
+
372
+ "@radix-ui/react-use-effect-event": ["@radix-ui/react-use-effect-event@0.0.2", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA=="],
373
+
374
+ "@radix-ui/react-use-escape-keydown": ["@radix-ui/react-use-escape-keydown@1.1.1", "", { "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g=="],
375
+
376
+ "@radix-ui/react-use-layout-effect": ["@radix-ui/react-use-layout-effect@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ=="],
377
+
378
+ "@react-native/assets-registry": ["@react-native/assets-registry@0.83.2", "", {}, "sha512-9I5l3pGAKnlpQ15uVkeB9Mgjvt3cZEaEc8EDtdexvdtZvLSjtwBzgourrOW4yZUijbjJr8h3YO2Y0q+THwUHTA=="],
379
+
380
+ "@react-native/babel-plugin-codegen": ["@react-native/babel-plugin-codegen@0.83.2", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@react-native/codegen": "0.83.2" } }, "sha512-XbcN/BEa64pVlb0Hb/E/Ph2SepjVN/FcNKrJcQvtaKZA6mBSO8pW8Eircdlr61/KBH94LihHbQoQDzkQFpeaTg=="],
381
+
382
+ "@react-native/babel-preset": ["@react-native/babel-preset@0.83.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-arrow-functions": "^7.24.7", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-computed-properties": "^7.24.7", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-function-name": "^7.25.1", "@babel/plugin-transform-literals": "^7.25.2", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-numeric-separator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.25.2", "@babel/plugin-transform-react-jsx-self": "^7.24.7", "@babel/plugin-transform-react-jsx-source": "^7.24.7", "@babel/plugin-transform-regenerator": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-shorthand-properties": "^7.24.7", "@babel/plugin-transform-spread": "^7.24.7", "@babel/plugin-transform-sticky-regex": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/template": "^7.25.0", "@react-native/babel-plugin-codegen": "0.83.2", "babel-plugin-syntax-hermes-parser": "0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "react-refresh": "^0.14.0" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-X/RAXDfe6W+om/Fw1i6htTxQXFhBJ2jgNOWx3WpI3KbjeIWbq7ib6vrpTeIAW2NUMg+K3mML1NzgD4dpZeqdjA=="],
383
+
384
+ "@react-native/codegen": ["@react-native/codegen@0.83.2", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.25.3", "glob": "^7.1.1", "hermes-parser": "0.32.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "yargs": "^17.6.2" }, "peerDependencies": { "@babel/core": "*" } }, "sha512-9uK6X1miCXqtL4c759l74N/XbQeneWeQVjoV7SD2CGJuW7ZefxaoYenwGPs7rMoCdtS6wuIyR3hXQ+uWEBGYXA=="],
385
+
386
+ "@react-native/community-cli-plugin": ["@react-native/community-cli-plugin@0.83.2", "", { "dependencies": { "@react-native/dev-middleware": "0.83.2", "debug": "^4.4.0", "invariant": "^2.2.4", "metro": "^0.83.3", "metro-config": "^0.83.3", "metro-core": "^0.83.3", "semver": "^7.1.3" }, "peerDependencies": { "@react-native-community/cli": "*", "@react-native/metro-config": "*" }, "optionalPeers": ["@react-native-community/cli", "@react-native/metro-config"] }, "sha512-sTEF0eiUKtmImEP07Qo5c3Khvm1LIVX1Qyb6zWUqPL6W3MqFiXutZvKBjqLz6p49Szx8cplQLoXfLHT0bcDXKg=="],
387
+
388
+ "@react-native/debugger-frontend": ["@react-native/debugger-frontend@0.83.2", "", {}, "sha512-t4fYfa7xopbUF5S4+ihNEwgaq4wLZLKLY0Ms8z72lkMteVd3bOX2Foxa8E2wTfRvdhPOkSpOsTeNDmD8ON4DoQ=="],
389
+
390
+ "@react-native/debugger-shell": ["@react-native/debugger-shell@0.83.2", "", { "dependencies": { "cross-spawn": "^7.0.6", "fb-dotslash": "0.5.8" } }, "sha512-z9go6NJMsLSDJT5MW6VGugRsZHjYvUTwxtsVc3uLt4U9W6T3J6FWI2wHpXIzd2dUkXRfAiRQ3Zi8ZQQ8fRFg9A=="],
391
+
392
+ "@react-native/dev-middleware": ["@react-native/dev-middleware@0.83.2", "", { "dependencies": { "@isaacs/ttlcache": "^1.4.1", "@react-native/debugger-frontend": "0.83.2", "@react-native/debugger-shell": "0.83.2", "chrome-launcher": "^0.15.2", "chromium-edge-launcher": "^0.2.0", "connect": "^3.6.5", "debug": "^4.4.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "open": "^7.0.3", "serve-static": "^1.16.2", "ws": "^7.5.10" } }, "sha512-Zi4EVaAm28+icD19NN07Gh8Pqg/84QQu+jn4patfWKNkcToRFP5vPEbbp0eLOGWS+BVB1d1Fn5lvMrJsBbFcOg=="],
393
+
394
+ "@react-native/gradle-plugin": ["@react-native/gradle-plugin@0.83.2", "", {}, "sha512-PqN11fXRAU+uJ0inZY1HWYlwJOXHOhF4SPyeHBBxjajKpm2PGunmvFWwkmBjmmUkP/CNO0ezTUudV0oj+2wiHQ=="],
395
+
396
+ "@react-native/js-polyfills": ["@react-native/js-polyfills@0.83.2", "", {}, "sha512-dk6fIY2OrKW/2Nk2HydfYNrQau8g6LOtd7NVBrgaqa+lvuRyIML5iimShP5qPqQnx2ofHuzjFw+Ya0b5Q7nDbA=="],
397
+
398
+ "@react-native/normalize-colors": ["@react-native/normalize-colors@0.83.2", "", {}, "sha512-gkZAb9LoVVzNuYzzOviH7DiPTXQoZPHuiTH2+O2+VWNtOkiznjgvqpwYAhg58a5zfRq5GXlbBdf5mzRj5+3Y5Q=="],
399
+
400
+ "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.83.2", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "*" } }, "sha512-N7mRjHLW/+KWxMp9IHRWyE3VIkeG1m3PnZJAGEFLCN8VFb7e4VfI567o7tE/HYcdcXCylw+Eqhlciz8gDeQ71g=="],
401
+
402
+ "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.15.3", "", { "dependencies": { "@react-navigation/elements": "^2.9.8", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.1.31", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-CIaHk5TuLeYlDgR1eij38kEbrQU5dAQxQZCC4Xv1wFZ/RRxppBopRMLzv2Il529a7mic6xG33OHcr9aEdOzq+A=="],
403
+
404
+ "@react-navigation/core": ["@react-navigation/core@7.15.1", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-Fqr6qxfZJIC4ewho7LtTa9zz6hcOzohX7D1lcDfrkGaYkS5xBwEZViGNxCJK/czUc74ua8NThyrObQFjB6Q/RQ=="],
405
+
406
+ "@react-navigation/elements": ["@react-navigation/elements@2.9.8", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.1.31", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-3gpwUmVnDJYvK9nFmAA/YXw0hmT/C/lZx8RkRMK+ux9l1T+32EWnQFnn34Wa1BMDX8HN2r64yrlW93DIzKI7Uw=="],
407
+
408
+ "@react-navigation/native": ["@react-navigation/native@7.1.31", "", { "dependencies": { "@react-navigation/core": "^7.15.1", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-+YCUwtfDgsux59Q0LDHc3Zid9ih93ecUCFWZOH6/+eNoUGnWx77wjS6ZfvBO/7E+EiIup11IVShDzCHR4of8hw=="],
409
+
410
+ "@react-navigation/native-stack": ["@react-navigation/native-stack@7.14.2", "", { "dependencies": { "@react-navigation/elements": "^2.9.8", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.1.31", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-/nKxFAFSUSGV+NSXrXXcWEcGAHdyp8RyWjoGMDzVPdBhjCLblVSgHWx5y4mm+k0de9V1pkjsftUaroP7rQckzw=="],
411
+
412
+ "@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="],
413
+
414
+ "@sinclair/typebox": ["@sinclair/typebox@0.27.10", "", {}, "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA=="],
415
+
416
+ "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
417
+
418
+ "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="],
419
+
420
+ "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
421
+
422
+ "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
423
+
424
+ "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
425
+
426
+ "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="],
427
+
428
+ "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
429
+
430
+ "@types/hammerjs": ["@types/hammerjs@2.0.46", "", {}, "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw=="],
431
+
432
+ "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
433
+
434
+ "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
435
+
436
+ "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
437
+
438
+ "@types/node": ["@types/node@25.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="],
439
+
440
+ "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
441
+
442
+ "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
443
+
444
+ "@types/yargs": ["@types/yargs@17.0.35", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg=="],
445
+
446
+ "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
447
+
448
+ "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
449
+
450
+ "@xmldom/xmldom": ["@xmldom/xmldom@0.8.11", "", {}, "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw=="],
451
+
452
+ "abort-controller": ["abort-controller@3.0.0", "", { "dependencies": { "event-target-shim": "^5.0.0" } }, "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg=="],
453
+
454
+ "accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="],
455
+
456
+ "acorn": ["acorn@8.16.0", "", { "bin": "bin/acorn" }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
457
+
458
+ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="],
459
+
460
+ "anser": ["anser@1.4.10", "", {}, "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww=="],
461
+
462
+ "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
463
+
464
+ "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
465
+
466
+ "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
467
+
468
+ "any-promise": ["any-promise@1.3.0", "", {}, "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A=="],
469
+
470
+ "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
471
+
472
+ "arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
473
+
474
+ "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
475
+
476
+ "aria-hidden": ["aria-hidden@1.2.6", "", { "dependencies": { "tslib": "^2.0.0" } }, "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA=="],
477
+
478
+ "array-timsort": ["array-timsort@1.0.3", "", {}, "sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ=="],
479
+
480
+ "asap": ["asap@2.0.6", "", {}, "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="],
481
+
482
+ "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="],
483
+
484
+ "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="],
485
+
486
+ "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
487
+
488
+ "babel-plugin-module-resolver": ["babel-plugin-module-resolver@5.0.2", "", { "dependencies": { "find-babel-config": "^2.1.1", "glob": "^9.3.3", "pkg-up": "^3.1.0", "reselect": "^4.1.7", "resolve": "^1.22.8" } }, "sha512-9KtaCazHee2xc0ibfqsDeamwDps6FZNo5S0Q81dUqEuFzVwPhcT4J5jOqIVvgCA3Q/wO9hKYxN/Ds3tIsp5ygg=="],
489
+
490
+ "babel-plugin-polyfill-corejs2": ["babel-plugin-polyfill-corejs2@0.4.15", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-define-polyfill-provider": "^0.6.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hR3GwrRwHUfYwGfrisXPIDP3JcYfBrW7wKE7+Au6wDYl7fm/ka1NEII6kORzxNU556JjfidZeBsO10kYvtV1aw=="],
491
+
492
+ "babel-plugin-polyfill-corejs3": ["babel-plugin-polyfill-corejs3@0.13.0", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.5", "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A=="],
493
+
494
+ "babel-plugin-polyfill-regenerator": ["babel-plugin-polyfill-regenerator@0.6.6", "", { "dependencies": { "@babel/helper-define-polyfill-provider": "^0.6.6" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "sha512-hYm+XLYRMvupxiQzrvXUj7YyvFFVfv5gI0R71AJzudg1g2AI2vyCPPIFEBjk162/wFzti3inBHo7isWFuEVS/A=="],
495
+
496
+ "babel-plugin-react-compiler": ["babel-plugin-react-compiler@1.0.0", "", { "dependencies": { "@babel/types": "^7.26.0" } }, "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw=="],
497
+
498
+ "babel-plugin-react-native-web": ["babel-plugin-react-native-web@0.21.2", "", {}, "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA=="],
499
+
500
+ "babel-plugin-syntax-hermes-parser": ["babel-plugin-syntax-hermes-parser@0.32.0", "", { "dependencies": { "hermes-parser": "0.32.0" } }, "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg=="],
501
+
502
+ "babel-plugin-transform-flow-enums": ["babel-plugin-transform-flow-enums@0.0.2", "", { "dependencies": { "@babel/plugin-syntax-flow": "^7.12.1" } }, "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ=="],
503
+
504
+ "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.2.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg=="],
505
+
506
+ "babel-preset-expo": ["babel-preset-expo@55.0.10", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/preset-react": "^7.22.15", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-preset": "0.83.2", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.32.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4", "resolve-from": "^5.0.0" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^55.0.2", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["expo-widgets"] }, "sha512-aRtW7qJKohGU2V0LUJ6IeP7py3+kVUo9zcc8+v1Kix8jGGuIvqvpo9S6W1Fmn9VFP2DBwkFDLiyzkCZS85urVA=="],
507
+
508
+ "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
509
+
510
+ "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
511
+
512
+ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
513
+
514
+ "baseline-browser-mapping": ["baseline-browser-mapping@2.10.0", "", { "bin": "dist/cli.cjs" }, "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA=="],
515
+
516
+ "better-opn": ["better-opn@3.0.2", "", { "dependencies": { "open": "^8.0.4" } }, "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ=="],
517
+
518
+ "big-integer": ["big-integer@1.6.52", "", {}, "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg=="],
519
+
520
+ "binary-extensions": ["binary-extensions@2.3.0", "", {}, "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw=="],
521
+
522
+ "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
523
+
524
+ "bplist-creator": ["bplist-creator@0.1.0", "", { "dependencies": { "stream-buffers": "2.2.x" } }, "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg=="],
525
+
526
+ "bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="],
527
+
528
+ "brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
529
+
530
+ "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
531
+
532
+ "browserslist": ["browserslist@4.28.1", "", { "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", "electron-to-chromium": "^1.5.263", "node-releases": "^2.0.27", "update-browserslist-db": "^1.2.0" }, "bin": "cli.js" }, "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA=="],
533
+
534
+ "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
535
+
536
+ "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
537
+
538
+ "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
539
+
540
+ "bytes": ["bytes@3.1.2", "", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="],
541
+
542
+ "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
543
+
544
+ "camelcase-css": ["camelcase-css@2.0.1", "", {}, "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="],
545
+
546
+ "caniuse-lite": ["caniuse-lite@1.0.30001775", "", {}, "sha512-s3Qv7Lht9zbVKE9XoTyRG6wVDCKdtOFIjBGg3+Yhn6JaytuNKPIjBMTMIY1AnOH3seL5mvF+x33oGAyK3hVt3A=="],
547
+
548
+ "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
549
+
550
+ "chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
551
+
552
+ "chrome-launcher": ["chrome-launcher@0.15.2", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0" }, "bin": { "print-chrome-path": "bin/print-chrome-path.js" } }, "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ=="],
553
+
554
+ "chromium-edge-launcher": ["chromium-edge-launcher@0.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^1.0.0", "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg=="],
555
+
556
+ "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
557
+
558
+ "cli-cursor": ["cli-cursor@2.1.0", "", { "dependencies": { "restore-cursor": "^2.0.0" } }, "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw=="],
559
+
560
+ "cli-spinners": ["cli-spinners@2.9.2", "", {}, "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg=="],
561
+
562
+ "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
563
+
564
+ "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
565
+
566
+ "clone": ["clone@1.0.4", "", {}, "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg=="],
567
+
568
+ "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
569
+
570
+ "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
571
+
572
+ "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
573
+
574
+ "color-string": ["color-string@1.9.1", "", { "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg=="],
575
+
576
+ "commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
577
+
578
+ "comment-json": ["comment-json@4.6.1", "", { "dependencies": { "array-timsort": "^1.0.3", "core-util-is": "^1.0.3", "esprima": "^4.0.1" } }, "sha512-kdBIsBGqD/sAeqvzeOhBvO/bhtpbfbIU/2lw7bp182FV1cVlY7gr1Jf3Q1I+NOsCk8e4gF5Sl9iYH5cNvVmx5w=="],
579
+
580
+ "compressible": ["compressible@2.0.18", "", { "dependencies": { "mime-db": ">= 1.43.0 < 2" } }, "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg=="],
581
+
582
+ "compression": ["compression@1.8.1", "", { "dependencies": { "bytes": "3.1.2", "compressible": "~2.0.18", "debug": "2.6.9", "negotiator": "~0.6.4", "on-headers": "~1.1.0", "safe-buffer": "5.2.1", "vary": "~1.1.2" } }, "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w=="],
583
+
584
+ "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
585
+
586
+ "connect": ["connect@3.7.0", "", { "dependencies": { "debug": "2.6.9", "finalhandler": "1.1.2", "parseurl": "~1.3.3", "utils-merge": "1.0.1" } }, "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ=="],
587
+
588
+ "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
589
+
590
+ "core-js-compat": ["core-js-compat@3.48.0", "", { "dependencies": { "browserslist": "^4.28.1" } }, "sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q=="],
591
+
592
+ "core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
593
+
594
+ "cross-fetch": ["cross-fetch@3.2.0", "", { "dependencies": { "node-fetch": "^2.7.0" } }, "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q=="],
595
+
596
+ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
597
+
598
+ "css-in-js-utils": ["css-in-js-utils@3.1.0", "", { "dependencies": { "hyphenate-style-name": "^1.0.3" } }, "sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A=="],
599
+
600
+ "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
601
+
602
+ "css-tree": ["css-tree@1.1.3", "", { "dependencies": { "mdn-data": "2.0.14", "source-map": "^0.6.1" } }, "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q=="],
603
+
604
+ "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
605
+
606
+ "cssesc": ["cssesc@3.0.0", "", { "bin": "bin/cssesc" }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
607
+
608
+ "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
609
+
610
+ "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
611
+
612
+ "decode-uri-component": ["decode-uri-component@0.2.2", "", {}, "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ=="],
613
+
614
+ "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
615
+
616
+ "defaults": ["defaults@1.0.4", "", { "dependencies": { "clone": "^1.0.2" } }, "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A=="],
617
+
618
+ "define-lazy-prop": ["define-lazy-prop@2.0.0", "", {}, "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og=="],
619
+
620
+ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="],
621
+
622
+ "destroy": ["destroy@1.2.0", "", {}, "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg=="],
623
+
624
+ "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
625
+
626
+ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="],
627
+
628
+ "didyoumean": ["didyoumean@1.2.2", "", {}, "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="],
629
+
630
+ "dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
631
+
632
+ "dnssd-advertise": ["dnssd-advertise@1.1.3", "", {}, "sha512-XENsHi3MBzWOCAXif3yZvU1Ah0l+nhJj1sjWL6TnOAYKvGiFhbTx32xHN7+wLMLUOCj7Nr0evADWG4R8JtqCDA=="],
633
+
634
+ "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
635
+
636
+ "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
637
+
638
+ "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
639
+
640
+ "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
641
+
642
+ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="],
643
+
644
+ "electron-to-chromium": ["electron-to-chromium@1.5.302", "", {}, "sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg=="],
645
+
646
+ "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
647
+
648
+ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="],
649
+
650
+ "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
651
+
652
+ "error-stack-parser": ["error-stack-parser@2.1.4", "", { "dependencies": { "stackframe": "^1.3.4" } }, "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ=="],
653
+
654
+ "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
655
+
656
+ "escape-html": ["escape-html@1.0.3", "", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="],
657
+
658
+ "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
659
+
660
+ "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
661
+
662
+ "etag": ["etag@1.8.1", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],
663
+
664
+ "event-target-shim": ["event-target-shim@5.0.1", "", {}, "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ=="],
665
+
666
+ "events": ["events@3.3.0", "", {}, "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q=="],
667
+
668
+ "expo": ["expo@55.0.4", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "55.0.14", "@expo/config": "~55.0.8", "@expo/config-plugins": "~55.0.6", "@expo/devtools": "55.0.2", "@expo/fingerprint": "0.16.5", "@expo/local-build-cache-provider": "55.0.6", "@expo/log-box": "55.0.7", "@expo/metro": "~54.2.0", "@expo/metro-config": "55.0.9", "@expo/vector-icons": "^15.0.2", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~55.0.10", "expo-asset": "~55.0.8", "expo-constants": "~55.0.7", "expo-file-system": "~55.0.10", "expo-font": "~55.0.4", "expo-keep-awake": "~55.0.4", "expo-modules-autolinking": "55.0.8", "expo-modules-core": "55.0.13", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.1" }, "peerDependencies": { "@expo/dom-webview": "*", "@expo/metro-runtime": "*", "react": "*", "react-native": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/metro-runtime", "react-native-webview"], "bin": { "expo": "bin/cli", "expo-modules-autolinking": "bin/autolinking", "fingerprint": "bin/fingerprint" } }, "sha512-cbQBPYwmH6FRvh942KR8mSdEcrVdsIMkjdHthtf59zlpzgrk28FabhOdL/Pc9WuS+CsIP3EIQbZqmLkTjv6qPg=="],
669
+
670
+ "expo-asset": ["expo-asset@55.0.8", "", { "dependencies": { "@expo/image-utils": "^0.8.12", "expo-constants": "~55.0.7" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-yEz2svDX67R0yiW2skx6dJmcE0q7sj9ECpGMcxBExMCbctc+nMoZCnjUuhzPl5vhClUsO5HFFXS5vIGmf1bgHQ=="],
671
+
672
+ "expo-audio": ["expo-audio@55.0.8", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-X61pQSikE2rsP2ZTMFUMThOmgGyYEHcmZpGVMrKJgcYtRCFKuctB/z69dFQPoumL+zTz8qlBoGohjkHVvA9P8A=="],
673
+
674
+ "expo-constants": ["expo-constants@55.0.7", "", { "dependencies": { "@expo/config": "~55.0.8", "@expo/env": "~2.1.1" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-kdcO4TsQRRqt0USvjaY5vgQMO9H52K3kBZ/ejC7F6rz70mv08GoowrZ1CYOr5O4JpPDRlIpQfZJUucaS/c+KWQ=="],
675
+
676
+ "expo-file-system": ["expo-file-system@55.0.10", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-ysFdVdUgtfj2ApY0Cn+pBg+yK4xp+SNwcaH8j2B91JJQ4OXJmnyCSmrNZYz7J4mdYVuv2GzxIP+N/IGlHQG3Yw=="],
677
+
678
+ "expo-font": ["expo-font@55.0.4", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZKeGTFffPygvY5dM/9ATM2p7QDkhsaHopH7wFAWgP2lKzqUMS9B/RxCvw5CaObr9Ro7x9YptyeRKX2HmgmMfrg=="],
679
+
680
+ "expo-glass-effect": ["expo-glass-effect@55.0.7", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-G7Q9rUaEY0YC36fGE6irDljfsfvzz/y49zagARAKvSJSyQMUSrhR25WOr5LK5Cw7gQNNBEy9U1ctlr7yCay/fQ=="],
681
+
682
+ "expo-haptics": ["expo-haptics@55.0.8", "", { "peerDependencies": { "expo": "*" } }, "sha512-yVR6EsQwl1WuhFITc0PpfI/7dsBdjK/F2YA8xB80UUW9iTa+Tqz21FpH4n/vtbargpzFxkhl5WNYMa419+QWFQ=="],
683
+
684
+ "expo-image": ["expo-image@55.0.5", "", { "dependencies": { "sf-symbols-typescript": "^2.2.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*", "react-native-web": "*" } }, "sha512-oejmMwy5O9EtC8po9NxkcurWHqND6p8xuJaj9FGNo8NXLt9e+w3cKWx7HuPzkH5y3qFXQ9Od+z+I/wxEci36fw=="],
685
+
686
+ "expo-image-loader": ["expo-image-loader@55.0.0", "", { "peerDependencies": { "expo": "*" } }, "sha512-NOjp56wDrfuA5aiNAybBIjqIn1IxKeGJ8CECWZncQ/GzjZfyTYAHTCyeApYkdKkMBLHINzI4BbTGSlbCa0fXXQ=="],
687
+
688
+ "expo-image-picker": ["expo-image-picker@55.0.11", "", { "dependencies": { "expo-image-loader": "~55.0.0" }, "peerDependencies": { "expo": "*" } }, "sha512-geJklIGdAR2N16iSk86oyJe7QgX5RpqDX1FjKpxO53fF4D0eBmg5Irm6gRwT0b+DHP1kJevZgzzbVJsRAV362g=="],
689
+
690
+ "expo-keep-awake": ["expo-keep-awake@55.0.4", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-vwfdMtMS5Fxaon8gC0AiE70SpxTsHJ+rjeoVJl8kdfdbxczF7OIaVmfjFJ5Gfigd/WZiLqxhfZk34VAkXF4PNg=="],
691
+
692
+ "expo-linking": ["expo-linking@55.0.7", "", { "dependencies": { "expo-constants": "~55.0.7", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-MiGCedere1vzQTEi2aGrkzd7eh/rPSz4w6F3GMBuAJzYl+/0VhIuyhozpEGrueyDIXWfzaUVOcn3SfxVi+kwQQ=="],
693
+
694
+ "expo-modules-autolinking": ["expo-modules-autolinking@55.0.8", "", { "dependencies": { "@expo/require-utils": "^55.0.2", "@expo/spawn-async": "^1.7.2", "chalk": "^4.1.0", "commander": "^7.2.0" }, "bin": "bin/expo-modules-autolinking.js" }, "sha512-nrWB1pkNp7bR8ECUTgYUiJ2Pyh6AvxCBXZ+lyPlfl1TzEIGhwU1Yqr+d78eJDueXaW+9zKeE0HqrTZoLS3ve4A=="],
695
+
696
+ "expo-modules-core": ["expo-modules-core@55.0.13", "", { "dependencies": { "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-DYLQTOJAR7jD3M9S0sH9myZaPEtShdicHrPiWcupIXMeMkQxFzErx+adUI8gZPy4AU45BgeGgtaogRfT25iLfw=="],
697
+
698
+ "expo-router": ["expo-router@55.0.3", "", { "dependencies": { "@expo/metro-runtime": "^55.0.6", "@expo/schema-utils": "^55.0.2", "@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-tabs": "^1.1.12", "@react-navigation/bottom-tabs": "^7.10.1", "@react-navigation/native": "^7.1.28", "@react-navigation/native-stack": "^7.10.1", "client-only": "^0.0.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", "expo-glass-effect": "^55.0.7", "expo-image": "^55.0.5", "expo-server": "^55.0.6", "expo-symbols": "^55.0.4", "fast-deep-equal": "^3.1.3", "invariant": "^2.2.4", "nanoid": "^3.3.8", "query-string": "^7.1.3", "react-fast-compare": "^3.2.2", "react-native-is-edge-to-edge": "^1.2.1", "semver": "~7.6.3", "server-only": "^0.0.1", "sf-symbols-typescript": "^2.1.0", "shallowequal": "^1.1.0", "use-latest-callback": "^0.2.1", "vaul": "^1.1.2" }, "peerDependencies": { "@expo/log-box": "55.0.7", "@expo/metro-runtime": "^55.0.6", "@react-navigation/drawer": "^7.7.2", "@testing-library/react-native": ">= 13.2.0", "expo": "*", "expo-constants": "^55.0.7", "expo-linking": "^55.0.7", "react": "*", "react-dom": "*", "react-native": "*", "react-native-gesture-handler": "*", "react-native-reanimated": "*", "react-native-safe-area-context": ">= 5.4.0", "react-native-screens": "*", "react-native-web": "*", "react-server-dom-webpack": "~19.0.4 || ~19.1.5 || ~19.2.4" }, "optionalPeers": ["@react-navigation/drawer", "@testing-library/react-native", "react-server-dom-webpack"] }, "sha512-B3MQAeZq9B2SS5kgEybGqXYR0AY7QYM7fQ5E4bJwtvZLJjWPmWhDALhBpD26ovK/i1k0fi9VgW47FKJODxM5Jg=="],
699
+
700
+ "expo-secure-store": ["expo-secure-store@55.0.8", "", { "peerDependencies": { "expo": "*" } }, "sha512-8w9tQe8U6oRo5YIzqCqVhRrOnfoODNDoitBtLXEx+zS6WLUnkRq5kH7ViJuOgiM7PzLr9pvAliRiDOKyvFbTuQ=="],
701
+
702
+ "expo-server": ["expo-server@55.0.6", "", {}, "sha512-xI72FTm469FfuuBL2R5aNtthgH+GR7ygOpsx/KcPS0K8AZaZd7VjtEExbzn9/qyyYkWW3T+3dAmCDKOMX8gdmQ=="],
703
+
704
+ "expo-sharing": ["expo-sharing@55.0.11", "", { "dependencies": { "@expo/config-plugins": "^55.0.6", "@expo/config-types": "^55.0.5", "@expo/plist": "^0.5.2" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-YlVez832W0sYR2KJY4Dr8ON9aC+Wp8a/r40eQyhoHT9Tetkr2KBM7tWLT0CGKRuTTnrqJL1C51UacLkHJ9zmNA=="],
705
+
706
+ "expo-splash-screen": ["expo-splash-screen@55.0.10", "", { "dependencies": { "@expo/prebuild-config": "^55.0.8" }, "peerDependencies": { "expo": "*" } }, "sha512-RN5qqrxudxFlRIjLFr/Ifmt+mUCLRc0gs66PekP6flzNS/JYEuoCbwJ+NmUwwJtPA+vyy60DYiky0QmS98ydmQ=="],
707
+
708
+ "expo-status-bar": ["expo-status-bar@55.0.4", "", { "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-BPDjUXKqv1F9j2YNGLRZfkBEZXIEEpqj+t81y4c+4fdSN3Pos7goIHXgcl2ozbKQLgKRZQyNZQtbUgh5UjHYUQ=="],
709
+
710
+ "expo-symbols": ["expo-symbols@55.0.4", "", { "dependencies": { "@expo-google-fonts/material-symbols": "^0.4.1", "sf-symbols-typescript": "^2.0.0" }, "peerDependencies": { "expo": "*", "expo-font": "*", "react": "*", "react-native": "*" } }, "sha512-w9rxPlpta3gks0G4Tvpq/qQdiMp4R/XOeOzyjSruYUQakmsWbQBKA+Sd/fCVXs7qFJSvVTOGXiOhZm+YJRYZVg=="],
711
+
712
+ "expo-system-ui": ["expo-system-ui@55.0.9", "", { "dependencies": { "@react-native/normalize-colors": "0.83.2", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" } }, "sha512-8ygP1B0uFAFI8s7eHY2IcGnE83GhFeZYwHBr/fQ4dSXnc7iVT9zp2PvyTyiDiibQ69dBG+fauMQ4KlPcOO51kQ=="],
713
+
714
+ "expo-web-browser": ["expo-web-browser@55.0.9", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-PvAVsG401QmZabtTsYh1cYcpPiqvBPs8oiOkSrp0jIXnneiM466HxmeNtvo+fNxqJ2nwOBz9qLPiWRO91VBfsQ=="],
715
+
716
+ "exponential-backoff": ["exponential-backoff@3.1.3", "", {}, "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA=="],
717
+
718
+ "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
719
+
720
+ "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
721
+
722
+ "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="],
723
+
724
+ "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="],
725
+
726
+ "fb-dotslash": ["fb-dotslash@0.5.8", "", { "bin": { "dotslash": "bin/dotslash" } }, "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA=="],
727
+
728
+ "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
729
+
730
+ "fbjs": ["fbjs@3.0.5", "", { "dependencies": { "cross-fetch": "^3.1.5", "fbjs-css-vars": "^1.0.0", "loose-envify": "^1.0.0", "object-assign": "^4.1.0", "promise": "^7.1.1", "setimmediate": "^1.0.5", "ua-parser-js": "^1.0.35" } }, "sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg=="],
731
+
732
+ "fbjs-css-vars": ["fbjs-css-vars@1.0.2", "", {}, "sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ=="],
733
+
734
+ "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
735
+
736
+ "fetch-nodeshim": ["fetch-nodeshim@0.4.8", "", {}, "sha512-YW5vG33rabBq6JpYosLNoXoaMN69/WH26MeeX2hkDVjN6UlvRGq3Wkazl9H0kisH95aMu/HtHL64JUvv/+Nv/g=="],
737
+
738
+ "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
739
+
740
+ "filter-obj": ["filter-obj@1.1.0", "", {}, "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ=="],
741
+
742
+ "finalhandler": ["finalhandler@1.1.2", "", { "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" } }, "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA=="],
743
+
744
+ "find-babel-config": ["find-babel-config@2.1.2", "", { "dependencies": { "json5": "^2.2.3" } }, "sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg=="],
745
+
746
+ "find-up": ["find-up@3.0.0", "", { "dependencies": { "locate-path": "^3.0.0" } }, "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg=="],
747
+
748
+ "flow-enums-runtime": ["flow-enums-runtime@0.0.6", "", {}, "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw=="],
749
+
750
+ "fontfaceobserver": ["fontfaceobserver@2.3.0", "", {}, "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg=="],
751
+
752
+ "fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="],
753
+
754
+ "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
755
+
756
+ "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
757
+
758
+ "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
759
+
760
+ "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
761
+
762
+ "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
763
+
764
+ "get-nonce": ["get-nonce@1.0.1", "", {}, "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q=="],
765
+
766
+ "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
767
+
768
+ "getenv": ["getenv@2.0.0", "", {}, "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ=="],
769
+
770
+ "glob": ["glob@9.3.5", "", { "dependencies": { "fs.realpath": "^1.0.0", "minimatch": "^8.0.2", "minipass": "^4.2.4", "path-scurry": "^1.6.1" } }, "sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q=="],
771
+
772
+ "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
773
+
774
+ "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
775
+
776
+ "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
777
+
778
+ "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
779
+
780
+ "hermes-compiler": ["hermes-compiler@0.14.1", "", {}, "sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA=="],
781
+
782
+ "hermes-estree": ["hermes-estree@0.32.0", "", {}, "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ=="],
783
+
784
+ "hermes-parser": ["hermes-parser@0.32.0", "", { "dependencies": { "hermes-estree": "0.32.0" } }, "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw=="],
785
+
786
+ "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="],
787
+
788
+ "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="],
789
+
790
+ "http-errors": ["http-errors@2.0.1", "", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="],
791
+
792
+ "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
793
+
794
+ "hyphenate-style-name": ["hyphenate-style-name@1.1.0", "", {}, "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw=="],
795
+
796
+ "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
797
+
798
+ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
799
+
800
+ "image-size": ["image-size@1.2.1", "", { "dependencies": { "queue": "6.0.2" }, "bin": "bin/image-size.js" }, "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw=="],
801
+
802
+ "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
803
+
804
+ "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
805
+
806
+ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
807
+
808
+ "inline-style-prefixer": ["inline-style-prefixer@7.0.1", "", { "dependencies": { "css-in-js-utils": "^3.1.0" } }, "sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw=="],
809
+
810
+ "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="],
811
+
812
+ "is-arrayish": ["is-arrayish@0.3.4", "", {}, "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA=="],
813
+
814
+ "is-binary-path": ["is-binary-path@2.1.0", "", { "dependencies": { "binary-extensions": "^2.0.0" } }, "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw=="],
815
+
816
+ "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="],
817
+
818
+ "is-docker": ["is-docker@2.2.1", "", { "bin": "cli.js" }, "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ=="],
819
+
820
+ "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="],
821
+
822
+ "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
823
+
824
+ "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
825
+
826
+ "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="],
827
+
828
+ "is-wsl": ["is-wsl@2.2.0", "", { "dependencies": { "is-docker": "^2.0.0" } }, "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww=="],
829
+
830
+ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
831
+
832
+ "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
833
+
834
+ "istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
835
+
836
+ "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
837
+
838
+ "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
839
+
840
+ "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="],
841
+
842
+ "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
843
+
844
+ "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
845
+
846
+ "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="],
847
+
848
+ "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
849
+
850
+ "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="],
851
+
852
+ "jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="],
853
+
854
+ "jimp-compact": ["jimp-compact@0.16.1", "", {}, "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww=="],
855
+
856
+ "jiti": ["jiti@1.21.7", "", { "bin": "bin/jiti.js" }, "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A=="],
857
+
858
+ "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
859
+
860
+ "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
861
+
862
+ "jsc-safe-url": ["jsc-safe-url@0.2.4", "", {}, "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q=="],
863
+
864
+ "jsesc": ["jsesc@3.1.0", "", { "bin": "bin/jsesc" }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
865
+
866
+ "json5": ["json5@2.2.3", "", { "bin": "lib/cli.js" }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
867
+
868
+ "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
869
+
870
+ "lan-network": ["lan-network@0.2.0", "", { "bin": "dist/lan-network-cli.js" }, "sha512-EZgbsXMrGS+oK+Ta12mCjzBFse+SIewGdwrSTr5g+MSymnjpox2x05ceI20PQejJOFvOgzcXrfDk/SdY7dSCtw=="],
871
+
872
+ "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
873
+
874
+ "lighthouse-logger": ["lighthouse-logger@1.4.2", "", { "dependencies": { "debug": "^2.6.9", "marky": "^1.2.2" } }, "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g=="],
875
+
876
+ "lightningcss": ["lightningcss@1.31.1", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.31.1", "lightningcss-darwin-arm64": "1.31.1", "lightningcss-darwin-x64": "1.31.1", "lightningcss-freebsd-x64": "1.31.1", "lightningcss-linux-arm-gnueabihf": "1.31.1", "lightningcss-linux-arm64-gnu": "1.31.1", "lightningcss-linux-arm64-musl": "1.31.1", "lightningcss-linux-x64-gnu": "1.31.1", "lightningcss-linux-x64-musl": "1.31.1", "lightningcss-win32-arm64-msvc": "1.31.1", "lightningcss-win32-x64-msvc": "1.31.1" } }, "sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ=="],
877
+
878
+ "lightningcss-android-arm64": ["lightningcss-android-arm64@1.31.1", "", { "os": "android", "cpu": "arm64" }, "sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg=="],
879
+
880
+ "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.31.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02uTEqf3vIfNMq3h/z2cJfcOXnQ0GRwQrkmPafhueLb2h7mqEidiCzkE4gBMEH65abHRiQvhdcQ+aP0D0g67sg=="],
881
+
882
+ "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.31.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-1ObhyoCY+tGxtsz1lSx5NXCj3nirk0Y0kB/g8B8DT+sSx4G9djitg9ejFnjb3gJNWo7qXH4DIy2SUHvpoFwfTA=="],
883
+
884
+ "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.31.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-1RINmQKAItO6ISxYgPwszQE1BrsVU5aB45ho6O42mu96UiZBxEXsuQ7cJW4zs4CEodPUioj/QrXW1r9pLUM74A=="],
885
+
886
+ "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.31.1", "", { "os": "linux", "cpu": "arm" }, "sha512-OOCm2//MZJ87CdDK62rZIu+aw9gBv4azMJuA8/KB74wmfS3lnC4yoPHm0uXZ/dvNNHmnZnB8XLAZzObeG0nS1g=="],
887
+
888
+ "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-WKyLWztD71rTnou4xAD5kQT+982wvca7E6QoLpoawZ1gP9JM0GJj4Tp5jMUh9B3AitHbRZ2/H3W5xQmdEOUlLg=="],
889
+
890
+ "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.31.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-mVZ7Pg2zIbe3XlNbZJdjs86YViQFoJSpc41CbVmKBPiGmC4YrfeOyz65ms2qpAobVd7WQsbW4PdsSJEMymyIMg=="],
891
+
892
+ "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-xGlFWRMl+0KvUhgySdIaReQdB4FNudfUTARn7q0hh/V67PVGCs3ADFjw+6++kG1RNd0zdGRlEKa+T13/tQjPMA=="],
893
+
894
+ "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.31.1", "", { "os": "linux", "cpu": "x64" }, "sha512-eowF8PrKHw9LpoZii5tdZwnBcYDxRw2rRCyvAXLi34iyeYfqCQNA9rmUM0ce62NlPhCvof1+9ivRaTY6pSKDaA=="],
895
+
896
+ "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.31.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-aJReEbSEQzx1uBlQizAOBSjcmr9dCdL3XuC/6HLXAxmtErsj2ICo5yYggg1qOODQMtnjNQv2UHb9NpOuFtYe4w=="],
897
+
898
+ "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.31.1", "", { "os": "win32", "cpu": "x64" }, "sha512-I9aiFrbd7oYHwlnQDqr1Roz+fTz61oDDJX7n9tYF9FJymH1cIN1DtKw3iYt6b8WZgEjoNwVSncwF4wx/ZedMhw=="],
899
+
900
+ "lilconfig": ["lilconfig@3.1.3", "", {}, "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw=="],
901
+
902
+ "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
903
+
904
+ "locate-path": ["locate-path@3.0.0", "", { "dependencies": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A=="],
905
+
906
+ "lodash.debounce": ["lodash.debounce@4.0.8", "", {}, "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="],
907
+
908
+ "lodash.throttle": ["lodash.throttle@4.1.1", "", {}, "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="],
909
+
910
+ "log-symbols": ["log-symbols@2.2.0", "", { "dependencies": { "chalk": "^2.0.1" } }, "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg=="],
911
+
912
+ "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
913
+
914
+ "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
915
+
916
+ "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="],
917
+
918
+ "marky": ["marky@1.3.0", "", {}, "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ=="],
919
+
920
+ "mdn-data": ["mdn-data@2.0.14", "", {}, "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow=="],
921
+
922
+ "memoize-one": ["memoize-one@5.2.1", "", {}, "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q=="],
923
+
924
+ "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
925
+
926
+ "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
927
+
928
+ "metro": ["metro@0.83.3", "", { "dependencies": { "@babel/code-frame": "^7.24.7", "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "@babel/types": "^7.25.2", "accepts": "^1.3.7", "chalk": "^4.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.32.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3", "mime-types": "^2.1.27", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": "src/cli.js" }, "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q=="],
929
+
930
+ "metro-babel-transformer": ["metro-babel-transformer@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.32.0", "nullthrows": "^1.1.1" } }, "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g=="],
931
+
932
+ "metro-cache": ["metro-cache@0.83.3", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.3" } }, "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q=="],
933
+
934
+ "metro-cache-key": ["metro-cache-key@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw=="],
935
+
936
+ "metro-config": ["metro-config@0.83.3", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.3", "metro-cache": "0.83.3", "metro-core": "0.83.3", "metro-runtime": "0.83.3", "yaml": "^2.6.1" } }, "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA=="],
937
+
938
+ "metro-core": ["metro-core@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.3" } }, "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw=="],
939
+
940
+ "metro-file-map": ["metro-file-map@0.83.3", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA=="],
941
+
942
+ "metro-minify-terser": ["metro-minify-terser@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ=="],
943
+
944
+ "metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="],
945
+
946
+ "metro-runtime": ["metro-runtime@0.83.3", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw=="],
947
+
948
+ "metro-source-map": ["metro-source-map@0.83.3", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg=="],
949
+
950
+ "metro-symbolicate": ["metro-symbolicate@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": "src/index.js" }, "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw=="],
951
+
952
+ "metro-transform-plugins": ["metro-transform-plugins@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/template": "^7.25.0", "@babel/traverse": "^7.25.3", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A=="],
953
+
954
+ "metro-transform-worker": ["metro-transform-worker@0.83.3", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.25.0", "@babel/parser": "^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-minify-terser": "0.83.3", "metro-source-map": "0.83.3", "metro-transform-plugins": "0.83.3", "nullthrows": "^1.1.1" } }, "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA=="],
955
+
956
+ "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
957
+
958
+ "mime": ["mime@1.6.0", "", { "bin": "cli.js" }, "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg=="],
959
+
960
+ "mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
961
+
962
+ "mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
963
+
964
+ "mimic-fn": ["mimic-fn@1.2.0", "", {}, "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="],
965
+
966
+ "minimatch": ["minimatch@8.0.7", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg=="],
967
+
968
+ "minipass": ["minipass@4.2.8", "", {}, "sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ=="],
969
+
970
+ "mkdirp": ["mkdirp@1.0.4", "", { "bin": "bin/cmd.js" }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="],
971
+
972
+ "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
973
+
974
+ "multitars": ["multitars@0.2.4", "", {}, "sha512-XgLbg1HHchFauMCQPRwMj6MSyDd5koPlTA1hM3rUFkeXzGpjU/I9fP3to7yrObE9jcN8ChIOQGrM0tV0kUZaKg=="],
975
+
976
+ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="],
977
+
978
+ "nanoid": ["nanoid@3.3.11", "", { "bin": "bin/nanoid.cjs" }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
979
+
980
+ "nativewind": ["nativewind@4.2.2", "", { "dependencies": { "comment-json": "^4.2.5", "debug": "^4.3.7", "react-native-css-interop": "0.2.2" }, "peerDependencies": { "tailwindcss": ">3.3.0" } }, "sha512-kUGbUamKUWdnAIjfBuhIrtDHFtMyL1pEE3AEbCuKeg656pHuB0KtJRk6Lrie/+8haj8hCSlwOleQFJLrE1sZgA=="],
981
+
982
+ "negotiator": ["negotiator@0.6.3", "", {}, "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="],
983
+
984
+ "node-fetch": ["node-fetch@2.7.0", "", { "dependencies": { "whatwg-url": "^5.0.0" }, "peerDependencies": { "encoding": "^0.1.0" }, "optionalPeers": ["encoding"] }, "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A=="],
985
+
986
+ "node-forge": ["node-forge@1.3.3", "", {}, "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg=="],
987
+
988
+ "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
989
+
990
+ "node-releases": ["node-releases@2.0.27", "", {}, "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA=="],
991
+
992
+ "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
993
+
994
+ "npm-package-arg": ["npm-package-arg@11.0.3", "", { "dependencies": { "hosted-git-info": "^7.0.0", "proc-log": "^4.0.0", "semver": "^7.3.5", "validate-npm-package-name": "^5.0.0" } }, "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw=="],
995
+
996
+ "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
997
+
998
+ "nullthrows": ["nullthrows@1.1.1", "", {}, "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw=="],
999
+
1000
+ "ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="],
1001
+
1002
+ "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
1003
+
1004
+ "object-hash": ["object-hash@3.0.0", "", {}, "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw=="],
1005
+
1006
+ "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="],
1007
+
1008
+ "on-headers": ["on-headers@1.1.0", "", {}, "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A=="],
1009
+
1010
+ "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
1011
+
1012
+ "onetime": ["onetime@2.0.1", "", { "dependencies": { "mimic-fn": "^1.0.0" } }, "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ=="],
1013
+
1014
+ "open": ["open@7.4.2", "", { "dependencies": { "is-docker": "^2.0.0", "is-wsl": "^2.1.1" } }, "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q=="],
1015
+
1016
+ "ora": ["ora@3.4.0", "", { "dependencies": { "chalk": "^2.4.2", "cli-cursor": "^2.1.0", "cli-spinners": "^2.0.0", "log-symbols": "^2.2.0", "strip-ansi": "^5.2.0", "wcwidth": "^1.0.1" } }, "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg=="],
1017
+
1018
+ "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
1019
+
1020
+ "p-locate": ["p-locate@3.0.0", "", { "dependencies": { "p-limit": "^2.0.0" } }, "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ=="],
1021
+
1022
+ "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
1023
+
1024
+ "parse-png": ["parse-png@2.1.0", "", { "dependencies": { "pngjs": "^3.3.0" } }, "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ=="],
1025
+
1026
+ "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],
1027
+
1028
+ "path-exists": ["path-exists@3.0.0", "", {}, "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ=="],
1029
+
1030
+ "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
1031
+
1032
+ "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
1033
+
1034
+ "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
1035
+
1036
+ "path-scurry": ["path-scurry@1.11.1", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
1037
+
1038
+ "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
1039
+
1040
+ "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
1041
+
1042
+ "pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
1043
+
1044
+ "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
1045
+
1046
+ "pkg-up": ["pkg-up@3.1.0", "", { "dependencies": { "find-up": "^3.0.0" } }, "sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA=="],
1047
+
1048
+ "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="],
1049
+
1050
+ "pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="],
1051
+
1052
+ "postcss": ["postcss@8.4.49", "", { "dependencies": { "nanoid": "^3.3.7", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA=="],
1053
+
1054
+ "postcss-import": ["postcss-import@15.1.0", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew=="],
1055
+
1056
+ "postcss-js": ["postcss-js@4.1.0", "", { "dependencies": { "camelcase-css": "^2.0.1" }, "peerDependencies": { "postcss": "^8.4.21" } }, "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw=="],
1057
+
1058
+ "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["tsx"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="],
1059
+
1060
+ "postcss-nested": ["postcss-nested@6.2.0", "", { "dependencies": { "postcss-selector-parser": "^6.1.1" }, "peerDependencies": { "postcss": "^8.2.14" } }, "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ=="],
1061
+
1062
+ "postcss-selector-parser": ["postcss-selector-parser@6.1.2", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg=="],
1063
+
1064
+ "postcss-value-parser": ["postcss-value-parser@4.2.0", "", {}, "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="],
1065
+
1066
+ "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
1067
+
1068
+ "proc-log": ["proc-log@4.2.0", "", {}, "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA=="],
1069
+
1070
+ "progress": ["progress@2.0.3", "", {}, "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="],
1071
+
1072
+ "promise": ["promise@8.3.0", "", { "dependencies": { "asap": "~2.0.6" } }, "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg=="],
1073
+
1074
+ "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
1075
+
1076
+ "query-string": ["query-string@7.1.3", "", { "dependencies": { "decode-uri-component": "^0.2.2", "filter-obj": "^1.1.0", "split-on-first": "^1.0.0", "strict-uri-encode": "^2.0.0" } }, "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg=="],
1077
+
1078
+ "queue": ["queue@6.0.2", "", { "dependencies": { "inherits": "~2.0.3" } }, "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA=="],
1079
+
1080
+ "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
1081
+
1082
+ "range-parser": ["range-parser@1.2.1", "", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="],
1083
+
1084
+ "react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
1085
+
1086
+ "react-devtools-core": ["react-devtools-core@6.1.5", "", { "dependencies": { "shell-quote": "^1.6.1", "ws": "^7" } }, "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA=="],
1087
+
1088
+ "react-dom": ["react-dom@19.2.4", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.4" } }, "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ=="],
1089
+
1090
+ "react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="],
1091
+
1092
+ "react-freeze": ["react-freeze@1.0.4", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA=="],
1093
+
1094
+ "react-is": ["react-is@19.2.4", "", {}, "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA=="],
1095
+
1096
+ "react-native": ["react-native@0.83.2", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.83.2", "@react-native/codegen": "0.83.2", "@react-native/community-cli-plugin": "0.83.2", "@react-native/gradle-plugin": "0.83.2", "@react-native/js-polyfills": "0.83.2", "@react-native/normalize-colors": "0.83.2", "@react-native/virtualized-lists": "0.83.2", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.32.0", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "hermes-compiler": "0.14.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.3", "metro-source-map": "^0.83.3", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.27.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.1", "react": "^19.2.0" }, "bin": "cli.js" }, "sha512-ZDma3SLkRN2U2dg0/EZqxNBAx4of/oTnPjXAQi299VLq2gdnbZowGy9hzqv+O7sTA62g+lM1v+2FM5DUnJ/6hg=="],
1097
+
1098
+ "react-native-css-interop": ["react-native-css-interop@0.2.2", "", { "dependencies": { "@babel/helper-module-imports": "^7.22.15", "@babel/traverse": "^7.23.0", "@babel/types": "^7.23.0", "debug": "^4.3.7", "lightningcss": "~1.27.0", "semver": "^7.6.3" }, "peerDependencies": { "react": ">=18", "react-native": "*", "react-native-reanimated": ">=3.6.2", "tailwindcss": "~3" } }, "sha512-2eUyl7RH1RT6TYbe5nm+d4HZ2Pr6Nmve158B57tb5W4Bo52Xzp+PFeWAdFnAr2HNB+r9b6qa8o3xH1YREVQU0g=="],
1099
+
1100
+ "react-native-draggable-flatlist": ["react-native-draggable-flatlist@4.0.3", "", { "dependencies": { "@babel/preset-typescript": "^7.17.12" }, "peerDependencies": { "react-native": ">=0.64.0", "react-native-gesture-handler": ">=2.0.0", "react-native-reanimated": ">=2.8.0" } }, "sha512-2F4x5BFieWdGq9SetD2nSAR7s7oQCSgNllYgERRXXtNfSOuAGAVbDb/3H3lP0y5f7rEyNwabKorZAD/SyyNbDw=="],
1101
+
1102
+ "react-native-gesture-handler": ["react-native-gesture-handler@2.30.0", "", { "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-5YsnKHGa0X9C8lb5oCnKm0fLUPM6CRduvUUw2Bav4RIj/C3HcFh4RIUnF8wgG6JQWCL1//gRx4v+LVWgcIQdGA=="],
1103
+
1104
+ "react-native-is-edge-to-edge": ["react-native-is-edge-to-edge@1.2.1", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q=="],
1105
+
1106
+ "react-native-reanimated": ["react-native-reanimated@4.2.1", "", { "dependencies": { "react-native-is-edge-to-edge": "1.2.1", "semver": "7.7.3" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": ">=0.7.0" } }, "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg=="],
1107
+
1108
+ "react-native-safe-area-context": ["react-native-safe-area-context@5.6.2", "", { "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg=="],
1109
+
1110
+ "react-native-screens": ["react-native-screens@4.23.0", "", { "dependencies": { "react-freeze": "^1.0.0", "warn-once": "^0.1.0" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw=="],
1111
+
1112
+ "react-native-svg": ["react-native-svg@15.15.3", "", { "dependencies": { "css-select": "^5.1.0", "css-tree": "^1.1.3", "warn-once": "0.1.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-/k4KYwPBLGcx2f5d4FjE+vCScK7QOX14cl2lIASJ28u4slHHtIhL0SZKU7u9qmRBHxTCKPoPBtN6haT1NENJNA=="],
1113
+
1114
+ "react-native-udp": ["react-native-udp@4.1.7", "", { "dependencies": { "buffer": "^5.6.0", "events": "^3.1.0" } }, "sha512-NUE3zewu61NCdSsLlj+l0ad6qojcVEZPT4hVG/x6DU9U4iCzwtfZSASh9vm7teAcVzLkdD+cO3411LHshAi/wA=="],
1115
+
1116
+ "react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
1117
+
1118
+ "react-native-worklets": ["react-native-worklets@0.7.2", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "7.27.1", "@babel/plugin-transform-class-properties": "7.27.1", "@babel/plugin-transform-classes": "7.28.4", "@babel/plugin-transform-nullish-coalescing-operator": "7.27.1", "@babel/plugin-transform-optional-chaining": "7.27.1", "@babel/plugin-transform-shorthand-properties": "7.27.1", "@babel/plugin-transform-template-literals": "7.27.1", "@babel/plugin-transform-unicode-regex": "7.27.1", "@babel/preset-typescript": "7.27.1", "convert-source-map": "2.0.0", "semver": "7.7.3" }, "peerDependencies": { "@babel/core": "*", "react": "*", "react-native": "*" } }, "sha512-DuLu1kMV/Uyl9pQHp3hehAlThoLw7Yk2FwRTpzASOmI+cd4845FWn3m2bk9MnjUw8FBRIyhwLqYm2AJaXDXsog=="],
1119
+
1120
+ "react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
1121
+
1122
+ "react-remove-scroll": ["react-remove-scroll@2.7.2", "", { "dependencies": { "react-remove-scroll-bar": "^2.3.7", "react-style-singleton": "^2.2.3", "tslib": "^2.1.0", "use-callback-ref": "^1.3.3", "use-sidecar": "^1.1.3" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q=="],
1123
+
1124
+ "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="],
1125
+
1126
+ "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="],
1127
+
1128
+ "read-cache": ["read-cache@1.0.0", "", { "dependencies": { "pify": "^2.3.0" } }, "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA=="],
1129
+
1130
+ "readdirp": ["readdirp@3.6.0", "", { "dependencies": { "picomatch": "^2.2.1" } }, "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA=="],
1131
+
1132
+ "regenerate": ["regenerate@1.4.2", "", {}, "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A=="],
1133
+
1134
+ "regenerate-unicode-properties": ["regenerate-unicode-properties@10.2.2", "", { "dependencies": { "regenerate": "^1.4.2" } }, "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g=="],
1135
+
1136
+ "regenerator-runtime": ["regenerator-runtime@0.13.11", "", {}, "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="],
1137
+
1138
+ "regexpu-core": ["regexpu-core@6.4.0", "", { "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", "unicode-match-property-value-ecmascript": "^2.2.1" } }, "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA=="],
1139
+
1140
+ "regjsgen": ["regjsgen@0.8.0", "", {}, "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q=="],
1141
+
1142
+ "regjsparser": ["regjsparser@0.13.0", "", { "dependencies": { "jsesc": "~3.1.0" }, "bin": "bin/parser" }, "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q=="],
1143
+
1144
+ "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
1145
+
1146
+ "reselect": ["reselect@4.1.8", "", {}, "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="],
1147
+
1148
+ "resolve": ["resolve@1.22.11", "", { "dependencies": { "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ=="],
1149
+
1150
+ "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
1151
+
1152
+ "resolve-workspace-root": ["resolve-workspace-root@2.0.1", "", {}, "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w=="],
1153
+
1154
+ "restore-cursor": ["restore-cursor@2.0.0", "", { "dependencies": { "onetime": "^2.0.0", "signal-exit": "^3.0.2" } }, "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q=="],
1155
+
1156
+ "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
1157
+
1158
+ "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": "bin.js" }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="],
1159
+
1160
+ "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
1161
+
1162
+ "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
1163
+
1164
+ "sax": ["sax@1.5.0", "", {}, "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA=="],
1165
+
1166
+ "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
1167
+
1168
+ "semver": ["semver@7.6.3", "", { "bin": "bin/semver.js" }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="],
1169
+
1170
+ "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="],
1171
+
1172
+ "serialize-error": ["serialize-error@2.1.0", "", {}, "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw=="],
1173
+
1174
+ "serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="],
1175
+
1176
+ "server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],
1177
+
1178
+ "setimmediate": ["setimmediate@1.0.5", "", {}, "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="],
1179
+
1180
+ "setprototypeof": ["setprototypeof@1.2.0", "", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="],
1181
+
1182
+ "sf-symbols-typescript": ["sf-symbols-typescript@2.2.0", "", {}, "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw=="],
1183
+
1184
+ "shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="],
1185
+
1186
+ "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
1187
+
1188
+ "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
1189
+
1190
+ "shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
1191
+
1192
+ "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
1193
+
1194
+ "simple-plist": ["simple-plist@1.3.1", "", { "dependencies": { "bplist-creator": "0.1.0", "bplist-parser": "0.3.1", "plist": "^3.0.5" } }, "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw=="],
1195
+
1196
+ "simple-swizzle": ["simple-swizzle@0.2.4", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw=="],
1197
+
1198
+ "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
1199
+
1200
+ "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
1201
+
1202
+ "slugify": ["slugify@1.6.6", "", {}, "sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw=="],
1203
+
1204
+ "source-map": ["source-map@0.5.7", "", {}, "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ=="],
1205
+
1206
+ "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
1207
+
1208
+ "source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
1209
+
1210
+ "split-on-first": ["split-on-first@1.1.0", "", {}, "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw=="],
1211
+
1212
+ "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
1213
+
1214
+ "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
1215
+
1216
+ "stackframe": ["stackframe@1.3.4", "", {}, "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw=="],
1217
+
1218
+ "stacktrace-parser": ["stacktrace-parser@0.1.11", "", { "dependencies": { "type-fest": "^0.7.1" } }, "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg=="],
1219
+
1220
+ "statuses": ["statuses@2.0.2", "", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="],
1221
+
1222
+ "stream-buffers": ["stream-buffers@2.2.0", "", {}, "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg=="],
1223
+
1224
+ "strict-uri-encode": ["strict-uri-encode@2.0.0", "", {}, "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ=="],
1225
+
1226
+ "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
1227
+
1228
+ "strip-ansi": ["strip-ansi@5.2.0", "", { "dependencies": { "ansi-regex": "^4.1.0" } }, "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA=="],
1229
+
1230
+ "structured-headers": ["structured-headers@0.4.1", "", {}, "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg=="],
1231
+
1232
+ "styleq": ["styleq@0.1.3", "", {}, "sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA=="],
1233
+
1234
+ "sucrase": ["sucrase@3.35.1", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", "tinyglobby": "^0.2.11", "ts-interface-checker": "^0.1.9" }, "bin": { "sucrase": "bin/sucrase", "sucrase-node": "bin/sucrase-node" } }, "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw=="],
1235
+
1236
+ "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
1237
+
1238
+ "supports-hyperlinks": ["supports-hyperlinks@2.3.0", "", { "dependencies": { "has-flag": "^4.0.0", "supports-color": "^7.0.0" } }, "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA=="],
1239
+
1240
+ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
1241
+
1242
+ "tailwindcss": ["tailwindcss@3.4.19", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.7", "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", "picocolors": "^1.1.1", "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", "postcss-nested": "^6.2.0", "postcss-selector-parser": "^6.1.2", "resolve": "^1.22.8", "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" } }, "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ=="],
1243
+
1244
+ "terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="],
1245
+
1246
+ "terser": ["terser@5.46.0", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": "bin/terser" }, "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg=="],
1247
+
1248
+ "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
1249
+
1250
+ "thenify": ["thenify@3.3.1", "", { "dependencies": { "any-promise": "^1.0.0" } }, "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw=="],
1251
+
1252
+ "thenify-all": ["thenify-all@1.6.0", "", { "dependencies": { "thenify": ">= 3.1.0 < 4" } }, "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA=="],
1253
+
1254
+ "throat": ["throat@5.0.0", "", {}, "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA=="],
1255
+
1256
+ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
1257
+
1258
+ "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="],
1259
+
1260
+ "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
1261
+
1262
+ "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="],
1263
+
1264
+ "toqr": ["toqr@0.1.1", "", {}, "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA=="],
1265
+
1266
+ "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
1267
+
1268
+ "ts-interface-checker": ["ts-interface-checker@0.1.13", "", {}, "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA=="],
1269
+
1270
+ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
1271
+
1272
+ "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="],
1273
+
1274
+ "type-fest": ["type-fest@0.7.1", "", {}, "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg=="],
1275
+
1276
+ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
1277
+
1278
+ "ua-parser-js": ["ua-parser-js@1.0.41", "", { "bin": "script/cli.js" }, "sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug=="],
1279
+
1280
+ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
1281
+
1282
+ "unicode-canonical-property-names-ecmascript": ["unicode-canonical-property-names-ecmascript@2.0.1", "", {}, "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg=="],
1283
+
1284
+ "unicode-match-property-ecmascript": ["unicode-match-property-ecmascript@2.0.0", "", { "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" } }, "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q=="],
1285
+
1286
+ "unicode-match-property-value-ecmascript": ["unicode-match-property-value-ecmascript@2.2.1", "", {}, "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg=="],
1287
+
1288
+ "unicode-property-aliases-ecmascript": ["unicode-property-aliases-ecmascript@2.2.0", "", {}, "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ=="],
1289
+
1290
+ "unpipe": ["unpipe@1.0.0", "", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="],
1291
+
1292
+ "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": "cli.js" }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="],
1293
+
1294
+ "use-callback-ref": ["use-callback-ref@1.3.3", "", { "dependencies": { "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg=="],
1295
+
1296
+ "use-latest-callback": ["use-latest-callback@0.2.6", "", { "peerDependencies": { "react": ">=16.8" } }, "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg=="],
1297
+
1298
+ "use-sidecar": ["use-sidecar@1.1.3", "", { "dependencies": { "detect-node-es": "^1.1.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ=="],
1299
+
1300
+ "use-sync-external-store": ["use-sync-external-store@1.6.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w=="],
1301
+
1302
+ "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
1303
+
1304
+ "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="],
1305
+
1306
+ "uuid": ["uuid@7.0.3", "", { "bin": "dist/bin/uuid" }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="],
1307
+
1308
+ "validate-npm-package-name": ["validate-npm-package-name@5.0.1", "", {}, "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ=="],
1309
+
1310
+ "vary": ["vary@1.1.2", "", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="],
1311
+
1312
+ "vaul": ["vaul@1.1.2", "", { "dependencies": { "@radix-ui/react-dialog": "^1.1.1" }, "peerDependencies": { "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" } }, "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA=="],
1313
+
1314
+ "vlq": ["vlq@1.0.1", "", {}, "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w=="],
1315
+
1316
+ "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="],
1317
+
1318
+ "warn-once": ["warn-once@0.1.1", "", {}, "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q=="],
1319
+
1320
+ "wcwidth": ["wcwidth@1.0.1", "", { "dependencies": { "defaults": "^1.0.3" } }, "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg=="],
1321
+
1322
+ "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
1323
+
1324
+ "whatwg-fetch": ["whatwg-fetch@3.6.20", "", {}, "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg=="],
1325
+
1326
+ "whatwg-url": ["whatwg-url@5.0.0", "", { "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" } }, "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw=="],
1327
+
1328
+ "whatwg-url-minimum": ["whatwg-url-minimum@0.1.1", "", {}, "sha512-u2FNVjFVFZhdjb502KzXy1gKn1mEisQRJssmSJT8CPhZdZa0AP6VCbWlXERKyGu0l09t0k50FiDiralpGhBxgA=="],
1329
+
1330
+ "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
1331
+
1332
+ "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
1333
+
1334
+ "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
1335
+
1336
+ "write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
1337
+
1338
+ "ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="],
1339
+
1340
+ "xcode": ["xcode@3.0.1", "", { "dependencies": { "simple-plist": "^1.1.0", "uuid": "^7.0.3" } }, "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA=="],
1341
+
1342
+ "xml2js": ["xml2js@0.6.0", "", { "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w=="],
1343
+
1344
+ "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="],
1345
+
1346
+ "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
1347
+
1348
+ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
1349
+
1350
+ "yaml": ["yaml@2.8.2", "", { "bin": "bin.mjs" }, "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A=="],
1351
+
1352
+ "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
1353
+
1354
+ "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
1355
+
1356
+ "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
1357
+
1358
+ "@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
1359
+
1360
+ "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
1361
+
1362
+ "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
1363
+
1364
+ "@babel/helper-create-class-features-plugin/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
1365
+
1366
+ "@babel/helper-create-regexp-features-plugin/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
1367
+
1368
+ "@babel/plugin-transform-runtime/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
1369
+
1370
+ "@expo/cli/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
1371
+
1372
+ "@expo/cli/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
1373
+
1374
+ "@expo/cli/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1375
+
1376
+ "@expo/cli/ws": ["ws@8.19.0", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg=="],
1377
+
1378
+ "@expo/config/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
1379
+
1380
+ "@expo/config/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1381
+
1382
+ "@expo/config-plugins/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
1383
+
1384
+ "@expo/config-plugins/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1385
+
1386
+ "@expo/devcert/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
1387
+
1388
+ "@expo/fingerprint/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
1389
+
1390
+ "@expo/fingerprint/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
1391
+
1392
+ "@expo/fingerprint/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1393
+
1394
+ "@expo/image-utils/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1395
+
1396
+ "@expo/metro-config/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="],
1397
+
1398
+ "@expo/metro-config/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
1399
+
1400
+ "@expo/prebuild-config/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1401
+
1402
+ "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
1403
+
1404
+ "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
1405
+
1406
+ "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.2", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": "bin/js-yaml.js" }, "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg=="],
1407
+
1408
+ "@radix-ui/react-collection/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1409
+
1410
+ "@radix-ui/react-dialog/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1411
+
1412
+ "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" } }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="],
1413
+
1414
+ "@react-native/babel-preset/@babel/plugin-transform-class-properties": ["@babel/plugin-transform-class-properties@7.28.6", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw=="],
1415
+
1416
+ "@react-native/babel-preset/@babel/plugin-transform-classes": ["@babel/plugin-transform-classes@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-globals": "^7.28.0", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-replace-supers": "^7.28.6", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q=="],
1417
+
1418
+ "@react-native/babel-preset/@babel/plugin-transform-nullish-coalescing-operator": ["@babel/plugin-transform-nullish-coalescing-operator@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg=="],
1419
+
1420
+ "@react-native/babel-preset/@babel/plugin-transform-optional-chaining": ["@babel/plugin-transform-optional-chaining@7.28.6", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w=="],
1421
+
1422
+ "@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
1423
+
1424
+ "@react-native/community-cli-plugin/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1425
+
1426
+ "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
1427
+
1428
+ "babel-plugin-polyfill-corejs2/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
1429
+
1430
+ "better-opn/open": ["open@8.4.2", "", { "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", "is-wsl": "^2.2.0" } }, "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ=="],
1431
+
1432
+ "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
1433
+
1434
+ "chokidar/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
1435
+
1436
+ "cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1437
+
1438
+ "compression/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1439
+
1440
+ "compression/negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="],
1441
+
1442
+ "connect/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1443
+
1444
+ "css-tree/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
1445
+
1446
+ "expo-modules-autolinking/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
1447
+
1448
+ "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
1449
+
1450
+ "fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
1451
+
1452
+ "fdir/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
1453
+
1454
+ "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1455
+
1456
+ "finalhandler/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="],
1457
+
1458
+ "finalhandler/on-finished": ["on-finished@2.3.0", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww=="],
1459
+
1460
+ "finalhandler/statuses": ["statuses@1.5.0", "", {}, "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA=="],
1461
+
1462
+ "hoist-non-react-statics/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
1463
+
1464
+ "istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
1465
+
1466
+ "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
1467
+
1468
+ "lighthouse-logger/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1469
+
1470
+ "log-symbols/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
1471
+
1472
+ "metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="],
1473
+
1474
+ "npm-package-arg/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1475
+
1476
+ "ora/chalk": ["chalk@2.4.2", "", { "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", "supports-color": "^5.3.0" } }, "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ=="],
1477
+
1478
+ "path-scurry/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
1479
+
1480
+ "pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
1481
+
1482
+ "react-native/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
1483
+
1484
+ "react-native/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1485
+
1486
+ "react-native-css-interop/lightningcss": ["lightningcss@1.27.0", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.27.0", "lightningcss-darwin-x64": "1.27.0", "lightningcss-freebsd-x64": "1.27.0", "lightningcss-linux-arm-gnueabihf": "1.27.0", "lightningcss-linux-arm64-gnu": "1.27.0", "lightningcss-linux-arm64-musl": "1.27.0", "lightningcss-linux-x64-gnu": "1.27.0", "lightningcss-linux-x64-musl": "1.27.0", "lightningcss-win32-arm64-msvc": "1.27.0", "lightningcss-win32-x64-msvc": "1.27.0" } }, "sha512-8f7aNmS1+etYSLHht0fQApPc2kNO8qGRutifN5rVIc6Xo6ABsEbqOr758UwI7ALVbTt4x1fllKt0PYgzD9S3yQ=="],
1487
+
1488
+ "react-native-css-interop/semver": ["semver@7.7.4", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
1489
+
1490
+ "react-native-reanimated/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
1491
+
1492
+ "react-native-web/@react-native/normalize-colors": ["@react-native/normalize-colors@0.74.89", "", {}, "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg=="],
1493
+
1494
+ "react-native-web/memoize-one": ["memoize-one@6.0.0", "", {}, "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw=="],
1495
+
1496
+ "react-native-worklets/@babel/preset-typescript": ["@babel/preset-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", "@babel/plugin-transform-typescript": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ=="],
1497
+
1498
+ "react-native-worklets/semver": ["semver@7.7.3", "", { "bin": "bin/semver.js" }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
1499
+
1500
+ "rimraf/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
1501
+
1502
+ "send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
1503
+
1504
+ "source-map-support/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
1505
+
1506
+ "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
1507
+
1508
+ "string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1509
+
1510
+ "strip-ansi/ansi-regex": ["ansi-regex@4.1.1", "", {}, "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g=="],
1511
+
1512
+ "sucrase/commander": ["commander@4.1.1", "", {}, "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA=="],
1513
+
1514
+ "terser/commander": ["commander@2.20.3", "", {}, "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ=="],
1515
+
1516
+ "test-exclude/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
1517
+
1518
+ "test-exclude/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
1519
+
1520
+ "tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
1521
+
1522
+ "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
1523
+
1524
+ "wrap-ansi/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
1525
+
1526
+ "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="],
1527
+
1528
+ "@expo/cli/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
1529
+
1530
+ "@expo/cli/glob/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
1531
+
1532
+ "@expo/cli/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
1533
+
1534
+ "@expo/config-plugins/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
1535
+
1536
+ "@expo/config-plugins/glob/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
1537
+
1538
+ "@expo/config-plugins/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
1539
+
1540
+ "@expo/config/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
1541
+
1542
+ "@expo/config/glob/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
1543
+
1544
+ "@expo/config/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
1545
+
1546
+ "@expo/fingerprint/glob/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
1547
+
1548
+ "@expo/fingerprint/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
1549
+
1550
+ "@expo/fingerprint/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
1551
+
1552
+ "@expo/metro-config/glob/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
1553
+
1554
+ "@expo/metro-config/glob/minipass": ["minipass@7.1.3", "", {}, "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A=="],
1555
+
1556
+ "@expo/metro-config/glob/path-scurry": ["path-scurry@2.0.2", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg=="],
1557
+
1558
+ "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
1559
+
1560
+ "@istanbuljs/load-nyc-config/find-up/path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
1561
+
1562
+ "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
1563
+
1564
+ "@react-native/codegen/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
1565
+
1566
+ "compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1567
+
1568
+ "connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1569
+
1570
+ "finalhandler/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1571
+
1572
+ "lighthouse-logger/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1573
+
1574
+ "log-symbols/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
1575
+
1576
+ "log-symbols/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
1577
+
1578
+ "log-symbols/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
1579
+
1580
+ "ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="],
1581
+
1582
+ "ora/chalk/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
1583
+
1584
+ "ora/chalk/supports-color": ["supports-color@5.5.0", "", { "dependencies": { "has-flag": "^3.0.0" } }, "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow=="],
1585
+
1586
+ "react-native-css-interop/lightningcss/detect-libc": ["detect-libc@1.0.3", "", { "bin": "bin/detect-libc.js" }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
1587
+
1588
+ "react-native-css-interop/lightningcss/lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.27.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Gl/lqIXY+d+ySmMbgDf0pgaWSqrWYxVHoc88q+Vhf2YNzZ8DwoRzGt5NZDVqqIW5ScpSnmmjcgXP87Dn2ylSSQ=="],
1589
+
1590
+ "react-native-css-interop/lightningcss/lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.27.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-0+mZa54IlcNAoQS9E0+niovhyjjQWEMrwW0p2sSdLRhLDc8LMQ/b67z7+B5q4VmjYCMSfnFi3djAAQFIDuj/Tg=="],
1591
+
1592
+ "react-native-css-interop/lightningcss/lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.27.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-n1sEf85fePoU2aDN2PzYjoI8gbBqnmLGEhKq7q0DKLj0UTVmOTwDC7PtLcy/zFxzASTSBlVQYJUhwIStQMIpRA=="],
1593
+
1594
+ "react-native-css-interop/lightningcss/lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.27.0", "", { "os": "linux", "cpu": "arm" }, "sha512-MUMRmtdRkOkd5z3h986HOuNBD1c2lq2BSQA1Jg88d9I7bmPGx08bwGcnB75dvr17CwxjxD6XPi3Qh8ArmKFqCA=="],
1595
+
1596
+ "react-native-css-interop/lightningcss/lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-cPsxo1QEWq2sfKkSq2Bq5feQDHdUEwgtA9KaB27J5AX22+l4l0ptgjMZZtYtUnteBofjee+0oW1wQ1guv04a7A=="],
1597
+
1598
+ "react-native-css-interop/lightningcss/lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.27.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-rCGBm2ax7kQ9pBSeITfCW9XSVF69VX+fm5DIpvDZQl4NnQoMQyRwhZQm9pd59m8leZ1IesRqWk2v/DntMo26lg=="],
1599
+
1600
+ "react-native-css-interop/lightningcss/lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Dk/jovSI7qqhJDiUibvaikNKI2x6kWPN79AQiD/E/KeQWMjdGe9kw51RAgoWFDi0coP4jinaH14Nrt/J8z3U4A=="],
1601
+
1602
+ "react-native-css-interop/lightningcss/lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.27.0", "", { "os": "linux", "cpu": "x64" }, "sha512-QKjTxXm8A9s6v9Tg3Fk0gscCQA1t/HMoF7Woy1u68wCk5kS4fR+q3vXa1p3++REW784cRAtkYKrPy6JKibrEZA=="],
1603
+
1604
+ "react-native-css-interop/lightningcss/lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.27.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-/wXegPS1hnhkeG4OXQKEMQeJd48RDC3qdh+OA8pCuOPCyvnm/yEayrJdJVqzBsqpy1aJklRCVxscpFur80o6iQ=="],
1605
+
1606
+ "react-native-css-interop/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.27.0", "", { "os": "win32", "cpu": "x64" }, "sha512-/OJLj94Zm/waZShL8nB5jsNj3CfNATLCTyFxZyouilfTmSoLDX7VlVAmhPHoZWVFp4vdmoiEbPEYC8HID3m6yw=="],
1607
+
1608
+ "react-native/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
1609
+
1610
+ "rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="],
1611
+
1612
+ "send/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
1613
+
1614
+ "test-exclude/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
1615
+
1616
+ "@expo/cli/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
1617
+
1618
+ "@expo/cli/glob/path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
1619
+
1620
+ "@expo/config-plugins/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
1621
+
1622
+ "@expo/config-plugins/glob/path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
1623
+
1624
+ "@expo/config/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
1625
+
1626
+ "@expo/config/glob/path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
1627
+
1628
+ "@expo/fingerprint/glob/path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
1629
+
1630
+ "@expo/fingerprint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
1631
+
1632
+ "@expo/metro-config/glob/minimatch/brace-expansion": ["brace-expansion@5.0.4", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg=="],
1633
+
1634
+ "@expo/metro-config/glob/path-scurry/lru-cache": ["lru-cache@11.2.6", "", {}, "sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ=="],
1635
+
1636
+ "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
1637
+
1638
+ "@react-native/codegen/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
1639
+
1640
+ "log-symbols/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
1641
+
1642
+ "log-symbols/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
1643
+
1644
+ "ora/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="],
1645
+
1646
+ "ora/chalk/supports-color/has-flag": ["has-flag@3.0.0", "", {}, "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="],
1647
+
1648
+ "react-native/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
1649
+
1650
+ "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="],
1651
+
1652
+ "@expo/cli/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
1653
+
1654
+ "@expo/config-plugins/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
1655
+
1656
+ "@expo/config/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
1657
+
1658
+ "@expo/metro-config/glob/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
1659
+
1660
+ "log-symbols/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
1661
+
1662
+ "ora/chalk/ansi-styles/color-convert/color-name": ["color-name@1.1.3", "", {}, "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="],
1663
+ }
1664
+}
components/SessionDrawer.tsx
....@@ -0,0 +1,564 @@
1
+/**
2
+ * SessionDrawer — Gmail-style left drawer for session navigation.
3
+ *
4
+ * Slides in from the left with a dark overlay. Shows sessions with
5
+ * unread badges. Swipe left to remove, long press to rename.
6
+ */
7
+import React, { useCallback, useEffect, useRef, useState } from "react";
8
+import {
9
+ Animated,
10
+ Dimensions,
11
+ Keyboard,
12
+ LayoutAnimation,
13
+ Pressable,
14
+ StyleSheet,
15
+ Text,
16
+ TextInput,
17
+ View,
18
+} from "react-native";
19
+import {
20
+ GestureHandlerRootView,
21
+ Swipeable,
22
+} from "react-native-gesture-handler";
23
+import DraggableFlatList, {
24
+ RenderItemParams,
25
+ ScaleDecorator,
26
+} from "react-native-draggable-flatlist";
27
+import * as Haptics from "expo-haptics";
28
+import { WsSession } from "../types";
29
+import { useChat } from "../contexts/ChatContext";
30
+import { useTheme, type ThemeColors } from "../contexts/ThemeContext";
31
+
32
+const SCREEN_WIDTH = Dimensions.get("window").width;
33
+const DRAWER_WIDTH = SCREEN_WIDTH * 0.82;
34
+const ROW_WIDTH = DRAWER_WIDTH;
35
+
36
+interface SessionDrawerProps {
37
+ visible: boolean;
38
+ onClose: () => void;
39
+}
40
+
41
+/* ── Swipeable row ── */
42
+
43
+function SessionRow({
44
+ session,
45
+ unreadCount,
46
+ onSwitch,
47
+ onLongPress,
48
+ onDelete,
49
+ onDrag,
50
+ isDragging,
51
+ colors,
52
+}: {
53
+ session: WsSession;
54
+ unreadCount: number;
55
+ onSwitch: () => void;
56
+ onLongPress: () => void;
57
+ onDelete: () => void;
58
+ onDrag: () => void;
59
+ isDragging: boolean;
60
+ colors: ThemeColors;
61
+}) {
62
+ const swipeRef = useRef<Swipeable>(null);
63
+
64
+ const renderRightActions = (
65
+ _progress: Animated.AnimatedInterpolation<number>,
66
+ dragX: Animated.AnimatedInterpolation<number>,
67
+ ) => {
68
+ const scale = dragX.interpolate({
69
+ inputRange: [-80, -40, 0],
70
+ outputRange: [1, 0.8, 0],
71
+ extrapolate: "clamp",
72
+ });
73
+ return (
74
+ <Pressable
75
+ onPress={() => {
76
+ swipeRef.current?.close();
77
+ onDelete();
78
+ }}
79
+ style={{
80
+ backgroundColor: colors.danger,
81
+ justifyContent: "center",
82
+ alignItems: "center",
83
+ width: 72,
84
+ borderRadius: 12,
85
+ marginLeft: 8,
86
+ }}
87
+ >
88
+ <Animated.Text
89
+ style={{
90
+ color: "#FFF",
91
+ fontSize: 13,
92
+ fontWeight: "600",
93
+ transform: [{ scale }],
94
+ }}
95
+ >
96
+ Remove
97
+ </Animated.Text>
98
+ </Pressable>
99
+ );
100
+ };
101
+
102
+ return (
103
+ <View style={{ width: ROW_WIDTH }}>
104
+ <Swipeable
105
+ ref={swipeRef}
106
+ renderRightActions={renderRightActions}
107
+ rightThreshold={50}
108
+ friction={2}
109
+ overshootRight={false}
110
+ >
111
+ <Pressable onPress={onSwitch} onLongPress={onLongPress} delayLongPress={400}>
112
+ <View
113
+ style={{
114
+ width: ROW_WIDTH,
115
+ flexDirection: "row",
116
+ alignItems: "center",
117
+ paddingVertical: 14,
118
+ paddingLeft: 16,
119
+ paddingRight: 12,
120
+ backgroundColor: session.isActive ? colors.accentBg : "transparent",
121
+ borderRadius: 12,
122
+ opacity: isDragging ? 0.8 : 1,
123
+ }}
124
+ >
125
+ {/* Icon — left */}
126
+ <View
127
+ style={{
128
+ width: 40,
129
+ height: 40,
130
+ borderRadius: 20,
131
+ backgroundColor: session.isActive ? colors.accent : colors.border,
132
+ alignItems: "center",
133
+ justifyContent: "center",
134
+ }}
135
+ >
136
+ {session.isActive ? (
137
+ <View
138
+ style={{
139
+ width: 10,
140
+ height: 10,
141
+ borderRadius: 5,
142
+ backgroundColor: "#FFF",
143
+ }}
144
+ />
145
+ ) : (
146
+ <Text style={{ color: colors.textSecondary, fontSize: 15, fontWeight: "700" }}>
147
+ {session.kind === "api" ? "A" : "T"}
148
+ </Text>
149
+ )}
150
+ </View>
151
+
152
+ {/* Name + subtitle — middle */}
153
+ <View style={{ flex: 1, marginLeft: 14 }}>
154
+ <Text
155
+ style={{
156
+ color: session.isActive ? colors.accent : colors.text,
157
+ fontSize: 17,
158
+ fontWeight: session.isActive ? "700" : "600",
159
+ }}
160
+ numberOfLines={1}
161
+ >
162
+ {session.name}
163
+ </Text>
164
+ <Text
165
+ style={{
166
+ color: colors.textMuted,
167
+ fontSize: 12,
168
+ marginTop: 2,
169
+ }}
170
+ numberOfLines={1}
171
+ >
172
+ {session.kind === "api" ? "Headless" : "Terminal"}
173
+ {session.isActive ? " — active" : ""}
174
+ </Text>
175
+ </View>
176
+
177
+ {/* Unread badge */}
178
+ {unreadCount > 0 && (
179
+ <View
180
+ style={{
181
+ minWidth: 24,
182
+ height: 24,
183
+ borderRadius: 12,
184
+ backgroundColor: colors.accent,
185
+ alignItems: "center",
186
+ justifyContent: "center",
187
+ paddingHorizontal: 7,
188
+ }}
189
+ >
190
+ <Text style={{ color: "#FFF", fontSize: 12, fontWeight: "700" }}>
191
+ {unreadCount > 99 ? "99+" : unreadCount}
192
+ </Text>
193
+ </View>
194
+ )}
195
+
196
+ {/* Drag handle */}
197
+ <Pressable
198
+ onPressIn={onDrag}
199
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
200
+ style={{
201
+ marginLeft: 8,
202
+ paddingHorizontal: 4,
203
+ paddingVertical: 8,
204
+ }}
205
+ >
206
+ <Text style={{ color: colors.textMuted, fontSize: 16, letterSpacing: 1 }}>⋮⋮</Text>
207
+ </Pressable>
208
+ </View>
209
+ </Pressable>
210
+ </Swipeable>
211
+ </View>
212
+ );
213
+}
214
+
215
+/* ── Inline rename ── */
216
+
217
+function RenameEditor({
218
+ name,
219
+ onConfirm,
220
+ onCancel,
221
+ colors,
222
+}: {
223
+ name: string;
224
+ onConfirm: (newName: string) => void;
225
+ onCancel: () => void;
226
+ colors: ThemeColors;
227
+}) {
228
+ const [editName, setEditName] = useState(name);
229
+ return (
230
+ <View style={{ paddingHorizontal: 16, paddingVertical: 8, width: ROW_WIDTH }}>
231
+ <TextInput
232
+ value={editName}
233
+ onChangeText={setEditName}
234
+ autoFocus
235
+ onSubmitEditing={() => onConfirm(editName.trim())}
236
+ onBlur={onCancel}
237
+ returnKeyType="done"
238
+ style={{
239
+ color: colors.text,
240
+ fontSize: 17,
241
+ fontWeight: "600",
242
+ borderBottomWidth: 2,
243
+ borderBottomColor: colors.accent,
244
+ paddingVertical: 10,
245
+ paddingHorizontal: 4,
246
+ }}
247
+ placeholderTextColor={colors.textMuted}
248
+ placeholder="Session name..."
249
+ />
250
+ </View>
251
+ );
252
+}
253
+
254
+/* ── Main Drawer ── */
255
+
256
+export function SessionDrawer({ visible, onClose }: SessionDrawerProps) {
257
+ const {
258
+ sessions,
259
+ activeSessionId,
260
+ requestSessions,
261
+ switchSession,
262
+ renameSession,
263
+ removeSession,
264
+ createSession,
265
+ unreadCounts,
266
+ } = useChat();
267
+ const { colors } = useTheme();
268
+ const [editingId, setEditingId] = useState<string | null>(null);
269
+ const slideAnim = useRef(new Animated.Value(-DRAWER_WIDTH)).current;
270
+ const fadeAnim = useRef(new Animated.Value(0)).current;
271
+ const [rendered, setRendered] = useState(false);
272
+
273
+ // Local ordering: merge server sessions while preserving user's drag order
274
+ const [orderedSessions, setOrderedSessions] = useState<WsSession[]>([]);
275
+ useEffect(() => {
276
+ setOrderedSessions((prev) => {
277
+ if (prev.length === 0) return sessions;
278
+ const serverIds = new Set(sessions.map((s) => s.id));
279
+ // Keep existing order, update session data, remove deleted
280
+ const kept = prev
281
+ .filter((p) => serverIds.has(p.id))
282
+ .map((p) => sessions.find((s) => s.id === p.id)!);
283
+ // Append any new sessions at the end
284
+ const keptIds = new Set(kept.map((s) => s.id));
285
+ const added = sessions.filter((s) => !keptIds.has(s.id));
286
+ return [...kept, ...added];
287
+ });
288
+ }, [sessions]);
289
+
290
+ useEffect(() => {
291
+ if (visible) {
292
+ Keyboard.dismiss();
293
+ setRendered(true);
294
+ requestSessions();
295
+ Animated.parallel([
296
+ Animated.spring(slideAnim, {
297
+ toValue: 0,
298
+ useNativeDriver: true,
299
+ damping: 22,
300
+ stiffness: 200,
301
+ }),
302
+ Animated.timing(fadeAnim, {
303
+ toValue: 1,
304
+ duration: 200,
305
+ useNativeDriver: true,
306
+ }),
307
+ ]).start();
308
+ } else {
309
+ Animated.parallel([
310
+ Animated.timing(slideAnim, {
311
+ toValue: -DRAWER_WIDTH,
312
+ duration: 200,
313
+ useNativeDriver: true,
314
+ }),
315
+ Animated.timing(fadeAnim, {
316
+ toValue: 0,
317
+ duration: 200,
318
+ useNativeDriver: true,
319
+ }),
320
+ ]).start(() => {
321
+ setRendered(false);
322
+ setEditingId(null);
323
+ });
324
+ }
325
+ }, [visible]);
326
+
327
+ const handleClose = useCallback(() => {
328
+ setEditingId(null);
329
+ Keyboard.dismiss();
330
+ onClose();
331
+ }, [onClose]);
332
+
333
+ const handleSwitch = useCallback(
334
+ (session: WsSession) => {
335
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
336
+ switchSession(session.id);
337
+ handleClose();
338
+ },
339
+ [switchSession, handleClose],
340
+ );
341
+
342
+ const handleStartRename = useCallback((session: WsSession) => {
343
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
344
+ setEditingId(session.id);
345
+ }, []);
346
+
347
+ const handleConfirmRename = useCallback(
348
+ (sessionId: string, newName: string) => {
349
+ if (newName) renameSession(sessionId, newName);
350
+ setEditingId(null);
351
+ },
352
+ [renameSession],
353
+ );
354
+
355
+ const handleRemove = useCallback(
356
+ (session: WsSession) => {
357
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
358
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
359
+ removeSession(session.id);
360
+ },
361
+ [removeSession],
362
+ );
363
+
364
+ const handleNewSession = useCallback(() => {
365
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
366
+ createSession();
367
+ handleClose();
368
+ setTimeout(() => requestSessions(), 2500);
369
+ }, [createSession, requestSessions, handleClose]);
370
+
371
+ const renderItem = useCallback(
372
+ ({ item, drag, isActive }: RenderItemParams<WsSession>) => {
373
+ if (editingId === item.id) {
374
+ return (
375
+ <RenameEditor
376
+ name={item.name}
377
+ onConfirm={(name) => handleConfirmRename(item.id, name)}
378
+ onCancel={() => setEditingId(null)}
379
+ colors={colors}
380
+ />
381
+ );
382
+ }
383
+ return (
384
+ <ScaleDecorator>
385
+ <SessionRow
386
+ session={item}
387
+ unreadCount={unreadCounts[item.id] ?? 0}
388
+ onSwitch={() => handleSwitch(item)}
389
+ onLongPress={() => handleStartRename(item)}
390
+ onDelete={() => handleRemove(item)}
391
+ onDrag={drag}
392
+ isDragging={isActive}
393
+ colors={colors}
394
+ />
395
+ </ScaleDecorator>
396
+ );
397
+ },
398
+ [editingId, unreadCounts, colors, handleSwitch, handleStartRename, handleRemove, handleConfirmRename],
399
+ );
400
+
401
+ const keyExtractor = useCallback((item: WsSession) => item.id, []);
402
+
403
+ const handleDragEnd = useCallback(
404
+ ({ data }: { data: WsSession[] }) => {
405
+ setOrderedSessions(data);
406
+ },
407
+ [],
408
+ );
409
+
410
+ const renderSeparator = useCallback(
411
+ () => (
412
+ <View
413
+ style={{
414
+ height: 1,
415
+ backgroundColor: colors.border,
416
+ marginHorizontal: 16,
417
+ marginVertical: 1,
418
+ }}
419
+ />
420
+ ),
421
+ [colors.border],
422
+ );
423
+
424
+ if (!rendered) return null;
425
+
426
+ return (
427
+ <View style={StyleSheet.absoluteFill} pointerEvents="box-none">
428
+ <View style={{ ...StyleSheet.absoluteFillObject, zIndex: 100 }}>
429
+ {/* Backdrop */}
430
+ <Animated.View
431
+ style={{
432
+ ...StyleSheet.absoluteFillObject,
433
+ backgroundColor: "rgba(0,0,0,0.5)",
434
+ opacity: fadeAnim,
435
+ }}
436
+ >
437
+ <Pressable style={{ flex: 1 }} onPress={handleClose} />
438
+ </Animated.View>
439
+
440
+ {/* Drawer panel */}
441
+ <Animated.View
442
+ style={{
443
+ position: "absolute",
444
+ top: 0,
445
+ bottom: 0,
446
+ left: 0,
447
+ width: DRAWER_WIDTH,
448
+ backgroundColor: colors.bgSecondary,
449
+ transform: [{ translateX: slideAnim }],
450
+ shadowColor: "#000",
451
+ shadowOffset: { width: 4, height: 0 },
452
+ shadowOpacity: 0.4,
453
+ shadowRadius: 12,
454
+ elevation: 20,
455
+ }}
456
+ >
457
+ <GestureHandlerRootView style={{ flex: 1 }}>
458
+ {/* Header */}
459
+ <View
460
+ style={{
461
+ paddingTop: 60,
462
+ paddingHorizontal: 20,
463
+ paddingBottom: 16,
464
+ borderBottomWidth: 1,
465
+ borderBottomColor: colors.border,
466
+ }}
467
+ >
468
+ <View
469
+ style={{
470
+ flexDirection: "row",
471
+ alignItems: "center",
472
+ justifyContent: "space-between",
473
+ }}
474
+ >
475
+ <Text
476
+ style={{
477
+ color: colors.text,
478
+ fontSize: 22,
479
+ fontWeight: "800",
480
+ letterSpacing: -0.5,
481
+ }}
482
+ >
483
+ Sessions
484
+ </Text>
485
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
486
+ <Pressable
487
+ onPress={() => requestSessions()}
488
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
489
+ style={({ pressed }) => ({
490
+ paddingHorizontal: 10,
491
+ paddingVertical: 5,
492
+ borderRadius: 10,
493
+ backgroundColor: pressed ? colors.bgTertiary : colors.bgTertiary + "80",
494
+ })}
495
+ >
496
+ <Text style={{ color: colors.textSecondary, fontSize: 13 }}>
497
+ Refresh
498
+ </Text>
499
+ </Pressable>
500
+ <Pressable
501
+ onPress={handleNewSession}
502
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
503
+ style={({ pressed }) => ({
504
+ width: 30,
505
+ height: 30,
506
+ borderRadius: 15,
507
+ alignItems: "center",
508
+ justifyContent: "center",
509
+ backgroundColor: pressed ? colors.accent + "CC" : colors.accent,
510
+ })}
511
+ >
512
+ <Text style={{ color: "#FFF", fontSize: 20, fontWeight: "600", marginTop: -1 }}>
513
+ +
514
+ </Text>
515
+ </Pressable>
516
+ </View>
517
+ </View>
518
+ </View>
519
+
520
+ {/* Session list */}
521
+ {orderedSessions.length === 0 ? (
522
+ <View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
523
+ <Text style={{ color: colors.textMuted, fontSize: 14 }}>
524
+ No sessions found
525
+ </Text>
526
+ </View>
527
+ ) : (
528
+ <DraggableFlatList
529
+ data={orderedSessions}
530
+ keyExtractor={keyExtractor}
531
+ renderItem={renderItem}
532
+ onDragEnd={handleDragEnd}
533
+ ItemSeparatorComponent={renderSeparator}
534
+ contentContainerStyle={{ paddingVertical: 4 }}
535
+ showsVerticalScrollIndicator={false}
536
+ keyboardShouldPersistTaps="handled"
537
+ />
538
+ )}
539
+
540
+ {/* Footer */}
541
+ <View
542
+ style={{
543
+ paddingVertical: 12,
544
+ paddingHorizontal: 20,
545
+ borderTopWidth: 1,
546
+ borderTopColor: colors.border,
547
+ }}
548
+ >
549
+ <Text
550
+ style={{
551
+ color: colors.textMuted,
552
+ fontSize: 11,
553
+ textAlign: "center",
554
+ }}
555
+ >
556
+ Tap to switch — Long press to rename — Swipe to remove
557
+ </Text>
558
+ </View>
559
+ </GestureHandlerRootView>
560
+ </Animated.View>
561
+ </View>
562
+ </View>
563
+ );
564
+}
components/SessionPicker.tsx
....@@ -1,289 +1,459 @@
1
-import React, { useCallback, useEffect, useState } from "react";
1
+import React, { useCallback, useEffect, useRef, useState } from "react";
22 import {
3
+ Animated,
4
+ Keyboard,
5
+ LayoutAnimation,
36 Modal,
7
+ Platform,
48 Pressable,
59 ScrollView,
610 Text,
711 TextInput,
12
+ UIManager,
813 View,
914 } from "react-native";
15
+import {
16
+ GestureHandlerRootView,
17
+ PanGestureHandler,
18
+ PanGestureHandlerGestureEvent,
19
+ State,
20
+ Swipeable,
21
+} from "react-native-gesture-handler";
1022 import * as Haptics from "expo-haptics";
1123 import { WsSession } from "../types";
1224 import { useChat } from "../contexts/ChatContext";
25
+
26
+if (
27
+ Platform.OS === "android" &&
28
+ UIManager.setLayoutAnimationEnabledExperimental
29
+) {
30
+ UIManager.setLayoutAnimationEnabledExperimental(true);
31
+}
1332
1433 interface SessionPickerProps {
1534 visible: boolean;
1635 onClose: () => void;
1736 }
1837
38
+/* ── Swipeable row with delete action ── */
39
+
40
+function SessionRow({
41
+ session,
42
+ onSwitch,
43
+ onLongPress,
44
+ onDelete,
45
+}: {
46
+ session: WsSession;
47
+ onSwitch: () => void;
48
+ onLongPress: () => void;
49
+ onDelete: () => void;
50
+}) {
51
+ const swipeRef = useRef<Swipeable>(null);
52
+
53
+ const renderRightActions = (
54
+ _progress: Animated.AnimatedInterpolation<number>,
55
+ dragX: Animated.AnimatedInterpolation<number>,
56
+ ) => {
57
+ const scale = dragX.interpolate({
58
+ inputRange: [-100, -50, 0],
59
+ outputRange: [1, 0.8, 0],
60
+ extrapolate: "clamp",
61
+ });
62
+
63
+ return (
64
+ <Pressable
65
+ onPress={() => {
66
+ swipeRef.current?.close();
67
+ onDelete();
68
+ }}
69
+ style={{
70
+ backgroundColor: "#FF3B30",
71
+ justifyContent: "center",
72
+ alignItems: "center",
73
+ width: 80,
74
+ borderRadius: 16,
75
+ marginLeft: 8,
76
+ }}
77
+ >
78
+ <Animated.Text
79
+ style={{
80
+ color: "#FFF",
81
+ fontSize: 14,
82
+ fontWeight: "600",
83
+ transform: [{ scale }],
84
+ }}
85
+ >
86
+ Remove
87
+ </Animated.Text>
88
+ </Pressable>
89
+ );
90
+ };
91
+
92
+ return (
93
+ <Swipeable
94
+ ref={swipeRef}
95
+ renderRightActions={renderRightActions}
96
+ rightThreshold={60}
97
+ friction={2}
98
+ overshootRight={false}
99
+ >
100
+ <Pressable
101
+ onPress={onSwitch}
102
+ onLongPress={onLongPress}
103
+ delayLongPress={400}
104
+ style={({ pressed }) => ({
105
+ width: "100%",
106
+ backgroundColor: pressed ? "#252538" : "#1E1E2E",
107
+ borderRadius: 16,
108
+ padding: 14,
109
+ flexDirection: "row",
110
+ alignItems: "center",
111
+ borderWidth: session.isActive ? 2 : 1,
112
+ borderColor: session.isActive ? "#4A9EFF" : "#2E2E45",
113
+ })}
114
+ >
115
+ {/* Number badge */}
116
+ <View
117
+ style={{
118
+ width: 32,
119
+ height: 32,
120
+ borderRadius: 16,
121
+ backgroundColor: session.isActive ? "#4A9EFF" : "#252538",
122
+ alignItems: "center",
123
+ justifyContent: "center",
124
+ marginRight: 12,
125
+ }}
126
+ >
127
+ <Text
128
+ style={{
129
+ color: session.isActive ? "#FFF" : "#9898B0",
130
+ fontSize: 14,
131
+ fontWeight: "700",
132
+ }}
133
+ >
134
+ {session.index}
135
+ </Text>
136
+ </View>
137
+
138
+ {/* Session info */}
139
+ <View style={{ flex: 1 }}>
140
+ <Text
141
+ style={{
142
+ color: "#E8E8F0",
143
+ fontSize: 16,
144
+ fontWeight: "600",
145
+ }}
146
+ numberOfLines={1}
147
+ >
148
+ {session.name}
149
+ </Text>
150
+ <Text
151
+ style={{
152
+ color: "#5A5A78",
153
+ fontSize: 11,
154
+ marginTop: 1,
155
+ }}
156
+ >
157
+ {session.kind === "api"
158
+ ? "Headless"
159
+ : session.kind === "visual"
160
+ ? "Visual"
161
+ : session.type === "terminal"
162
+ ? "Terminal"
163
+ : "Claude"}
164
+ {session.isActive ? " — active" : ""}
165
+ </Text>
166
+ </View>
167
+
168
+ {/* Active indicator */}
169
+ {session.isActive && (
170
+ <View
171
+ style={{
172
+ width: 8,
173
+ height: 8,
174
+ borderRadius: 4,
175
+ backgroundColor: "#2ED573",
176
+ }}
177
+ />
178
+ )}
179
+ </Pressable>
180
+ </Swipeable>
181
+ );
182
+}
183
+
184
+/* ── Inline rename editor ── */
185
+
186
+function RenameEditor({
187
+ name,
188
+ onConfirm,
189
+ onCancel,
190
+}: {
191
+ name: string;
192
+ onConfirm: (newName: string) => void;
193
+ onCancel: () => void;
194
+}) {
195
+ const [editName, setEditName] = useState(name);
196
+
197
+ return (
198
+ <View
199
+ style={{
200
+ backgroundColor: "#1E1E2E",
201
+ borderRadius: 16,
202
+ padding: 14,
203
+ borderWidth: 2,
204
+ borderColor: "#4A9EFF",
205
+ }}
206
+ >
207
+ <TextInput
208
+ value={editName}
209
+ onChangeText={setEditName}
210
+ autoFocus
211
+ onSubmitEditing={() => onConfirm(editName.trim())}
212
+ onBlur={onCancel}
213
+ returnKeyType="done"
214
+ style={{
215
+ color: "#E8E8F0",
216
+ fontSize: 16,
217
+ fontWeight: "600",
218
+ padding: 0,
219
+ marginBottom: 10,
220
+ }}
221
+ placeholderTextColor="#5A5A78"
222
+ placeholder="Session name..."
223
+ />
224
+ <View style={{ flexDirection: "row", gap: 8 }}>
225
+ <Pressable
226
+ onPress={() => onConfirm(editName.trim())}
227
+ style={{
228
+ flex: 1,
229
+ backgroundColor: "#4A9EFF",
230
+ borderRadius: 10,
231
+ paddingVertical: 8,
232
+ alignItems: "center",
233
+ }}
234
+ >
235
+ <Text style={{ color: "#FFF", fontSize: 14, fontWeight: "600" }}>
236
+ Save
237
+ </Text>
238
+ </Pressable>
239
+ <Pressable
240
+ onPress={onCancel}
241
+ style={{
242
+ flex: 1,
243
+ backgroundColor: "#252538",
244
+ borderRadius: 10,
245
+ paddingVertical: 8,
246
+ alignItems: "center",
247
+ }}
248
+ >
249
+ <Text style={{ color: "#9898B0", fontSize: 14 }}>Cancel</Text>
250
+ </Pressable>
251
+ </View>
252
+ </View>
253
+ );
254
+}
255
+
256
+/* ── Main SessionPicker ── */
257
+
19258 export function SessionPicker({ visible, onClose }: SessionPickerProps) {
20
- const { sessions, requestSessions, switchSession, renameSession } = useChat();
259
+ const {
260
+ sessions,
261
+ requestSessions,
262
+ switchSession,
263
+ renameSession,
264
+ removeSession,
265
+ } = useChat();
21266 const [editingId, setEditingId] = useState<string | null>(null);
22
- const [editName, setEditName] = useState("");
267
+ const [keyboardHeight, setKeyboardHeight] = useState(0);
268
+
269
+ // Sort: active first, then by index
270
+ const sortedSessions = [...sessions].sort((a, b) => {
271
+ if (a.isActive && !b.isActive) return -1;
272
+ if (!a.isActive && b.isActive) return 1;
273
+ return a.index - b.index;
274
+ });
23275
24276 useEffect(() => {
25
- if (visible) {
277
+ const showSub = Keyboard.addListener("keyboardWillShow", (e) =>
278
+ setKeyboardHeight(e.endCoordinates.height),
279
+ );
280
+ const hideSub = Keyboard.addListener("keyboardWillHide", () =>
281
+ setKeyboardHeight(0),
282
+ );
283
+ return () => {
284
+ showSub.remove();
285
+ hideSub.remove();
286
+ };
287
+ }, []);
288
+
289
+ useEffect(() => {
290
+ if (!visible) {
291
+ setEditingId(null);
292
+ } else {
26293 requestSessions();
27294 }
28295 }, [visible, requestSessions]);
296
+
297
+ const handleClose = useCallback(() => {
298
+ setEditingId(null);
299
+ Keyboard.dismiss();
300
+ onClose();
301
+ }, [onClose]);
29302
30303 const handleSwitch = useCallback(
31304 (session: WsSession) => {
32305 Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
33306 switchSession(session.id);
34
- onClose();
307
+ handleClose();
35308 },
36
- [switchSession, onClose]
309
+ [switchSession, handleClose],
37310 );
38311
39312 const handleStartRename = useCallback((session: WsSession) => {
313
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
40314 setEditingId(session.id);
41
- setEditName(session.name);
42315 }, []);
43316
44
- const handleConfirmRename = useCallback(() => {
45
- if (editingId && editName.trim()) {
46
- renameSession(editingId, editName.trim());
47
- }
48
- setEditingId(null);
49
- setEditName("");
50
- }, [editingId, editName, renameSession]);
317
+ const handleConfirmRename = useCallback(
318
+ (sessionId: string, newName: string) => {
319
+ if (newName) {
320
+ renameSession(sessionId, newName);
321
+ }
322
+ setEditingId(null);
323
+ },
324
+ [renameSession],
325
+ );
326
+
327
+ const handleRemove = useCallback(
328
+ (session: WsSession) => {
329
+ Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
330
+ LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
331
+ removeSession(session.id);
332
+ },
333
+ [removeSession],
334
+ );
51335
52336 return (
53337 <Modal
54338 visible={visible}
55339 animationType="slide"
56340 transparent
57
- onRequestClose={onClose}
341
+ onRequestClose={handleClose}
58342 >
59
- <View
60
- style={{
61
- flex: 1,
62
- backgroundColor: "rgba(0,0,0,0.6)",
63
- justifyContent: "flex-end",
64
- }}
65
- >
66
- <Pressable
67
- style={{ flex: 1 }}
68
- onPress={onClose}
69
- />
343
+ <GestureHandlerRootView style={{ flex: 1 }}>
70344 <View
71345 style={{
72
- backgroundColor: "#14141F",
73
- borderTopLeftRadius: 24,
74
- borderTopRightRadius: 24,
75
- maxHeight: "70%",
76
- paddingBottom: 40,
346
+ flex: 1,
347
+ backgroundColor: "rgba(0,0,0,0.6)",
348
+ justifyContent: "flex-end",
77349 }}
78350 >
79
- {/* Handle bar */}
80
- <View style={{ alignItems: "center", paddingTop: 12, paddingBottom: 8 }}>
81
- <View
82
- style={{
83
- width: 40,
84
- height: 4,
85
- borderRadius: 2,
86
- backgroundColor: "#2E2E45",
87
- }}
88
- />
89
- </View>
90
-
91
- {/* Header */}
351
+ <Pressable style={{ flex: 1 }} onPress={handleClose} />
92352 <View
93353 style={{
94
- flexDirection: "row",
95
- alignItems: "center",
96
- justifyContent: "space-between",
97
- paddingHorizontal: 20,
98
- paddingBottom: 16,
354
+ backgroundColor: "#14141F",
355
+ borderTopLeftRadius: 24,
356
+ borderTopRightRadius: 24,
357
+ maxHeight: "70%",
358
+ paddingBottom: Math.max(40, keyboardHeight),
99359 }}
100360 >
101
- <Text
361
+ {/* Handle bar */}
362
+ <View
363
+ style={{ alignItems: "center", paddingTop: 12, paddingBottom: 8 }}
364
+ >
365
+ <View
366
+ style={{
367
+ width: 40,
368
+ height: 4,
369
+ borderRadius: 2,
370
+ backgroundColor: "#2E2E45",
371
+ }}
372
+ />
373
+ </View>
374
+
375
+ {/* Header */}
376
+ <View
102377 style={{
103
- color: "#E8E8F0",
104
- fontSize: 20,
105
- fontWeight: "700",
378
+ flexDirection: "row",
379
+ alignItems: "center",
380
+ justifyContent: "space-between",
381
+ paddingHorizontal: 20,
382
+ paddingBottom: 12,
106383 }}
107384 >
108
- Sessions
109
- </Text>
110
- <Pressable
111
- onPress={() => requestSessions()}
112
- hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
113
- style={{
114
- paddingHorizontal: 12,
115
- paddingVertical: 6,
116
- borderRadius: 12,
117
- backgroundColor: "#1E1E2E",
118
- }}
385
+ <Text
386
+ style={{
387
+ color: "#E8E8F0",
388
+ fontSize: 20,
389
+ fontWeight: "700",
390
+ }}
391
+ >
392
+ Sessions
393
+ </Text>
394
+ <Pressable
395
+ onPress={() => requestSessions()}
396
+ hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
397
+ style={({ pressed }) => ({
398
+ paddingHorizontal: 12,
399
+ paddingVertical: 6,
400
+ borderRadius: 12,
401
+ backgroundColor: pressed ? "#252538" : "#1E1E2E",
402
+ })}
403
+ >
404
+ <Text style={{ color: "#9898B0", fontSize: 13 }}>Refresh</Text>
405
+ </Pressable>
406
+ </View>
407
+
408
+ {/* Session list */}
409
+ <ScrollView
410
+ style={{ paddingHorizontal: 16 }}
411
+ showsVerticalScrollIndicator={false}
412
+ keyboardShouldPersistTaps="handled"
119413 >
120
- <Text style={{ color: "#9898B0", fontSize: 13 }}>Refresh</Text>
121
- </Pressable>
122
- </View>
123
-
124
- {/* Session list */}
125
- <ScrollView
126
- style={{ paddingHorizontal: 16 }}
127
- showsVerticalScrollIndicator={false}
128
- >
129
- {sessions.length === 0 ? (
130
- <View style={{ alignItems: "center", paddingVertical: 32 }}>
131
- <Text style={{ color: "#5A5A78", fontSize: 15 }}>
132
- No sessions found
133
- </Text>
134
- </View>
135
- ) : (
136
- sessions.map((session) => (
137
- <View key={session.id} style={{ marginBottom: 8 }}>
138
- {editingId === session.id ? (
139
- /* Rename mode */
140
- <View
141
- style={{
142
- backgroundColor: "#1E1E2E",
143
- borderRadius: 16,
144
- padding: 16,
145
- borderWidth: 2,
146
- borderColor: "#4A9EFF",
147
- }}
148
- >
149
- <TextInput
150
- value={editName}
151
- onChangeText={setEditName}
152
- autoFocus
153
- onSubmitEditing={handleConfirmRename}
154
- returnKeyType="done"
155
- style={{
156
- color: "#E8E8F0",
157
- fontSize: 17,
158
- fontWeight: "600",
159
- padding: 0,
160
- marginBottom: 12,
161
- }}
162
- placeholderTextColor="#5A5A78"
163
- placeholder="Session name..."
164
- />
165
- <View style={{ flexDirection: "row", gap: 8 }}>
166
- <Pressable
167
- onPress={handleConfirmRename}
168
- style={{
169
- flex: 1,
170
- backgroundColor: "#4A9EFF",
171
- borderRadius: 10,
172
- paddingVertical: 10,
173
- alignItems: "center",
174
- }}
175
- >
176
- <Text style={{ color: "#FFF", fontSize: 15, fontWeight: "600" }}>
177
- Save
178
- </Text>
179
- </Pressable>
180
- <Pressable
181
- onPress={() => setEditingId(null)}
182
- style={{
183
- flex: 1,
184
- backgroundColor: "#252538",
185
- borderRadius: 10,
186
- paddingVertical: 10,
187
- alignItems: "center",
188
- }}
189
- >
190
- <Text style={{ color: "#9898B0", fontSize: 15 }}>Cancel</Text>
191
- </Pressable>
192
- </View>
193
- </View>
194
- ) : (
195
- /* Normal session row */
196
- <Pressable
197
- onPress={() => handleSwitch(session)}
198
- onLongPress={() => handleStartRename(session)}
199
- style={({ pressed }) => ({
200
- backgroundColor: pressed ? "#252538" : "#1E1E2E",
201
- borderRadius: 16,
202
- padding: 16,
203
- flexDirection: "row",
204
- alignItems: "center",
205
- borderWidth: session.isActive ? 2 : 1,
206
- borderColor: session.isActive ? "#4A9EFF" : "#2E2E45",
207
- })}
208
- >
209
- {/* Number badge */}
210
- <View
211
- style={{
212
- width: 36,
213
- height: 36,
214
- borderRadius: 18,
215
- backgroundColor: session.isActive ? "#4A9EFF" : "#252538",
216
- alignItems: "center",
217
- justifyContent: "center",
218
- marginRight: 14,
219
- }}
220
- >
221
- <Text
222
- style={{
223
- color: session.isActive ? "#FFF" : "#9898B0",
224
- fontSize: 16,
225
- fontWeight: "700",
226
- }}
227
- >
228
- {session.index}
229
- </Text>
230
- </View>
231
-
232
- {/* Session info */}
233
- <View style={{ flex: 1 }}>
234
- <Text
235
- style={{
236
- color: "#E8E8F0",
237
- fontSize: 17,
238
- fontWeight: "600",
239
- }}
240
- numberOfLines={1}
241
- >
242
- {session.name}
243
- </Text>
244
- <Text
245
- style={{
246
- color: "#5A5A78",
247
- fontSize: 12,
248
- marginTop: 2,
249
- }}
250
- >
251
- {session.kind === "api" ? "Headless" : session.kind === "visual" ? "Visual" : session.type === "terminal" ? "Terminal" : "Claude"}
252
- {session.isActive ? " — active" : ""}
253
- </Text>
254
- </View>
255
-
256
- {/* Active indicator */}
257
- {session.isActive && (
258
- <View
259
- style={{
260
- width: 10,
261
- height: 10,
262
- borderRadius: 5,
263
- backgroundColor: "#2ED573",
264
- }}
265
- />
266
- )}
267
- </Pressable>
268
- )}
414
+ {sortedSessions.length === 0 ? (
415
+ <View style={{ alignItems: "center", paddingVertical: 32 }}>
416
+ <Text style={{ color: "#5A5A78", fontSize: 15 }}>
417
+ No sessions found
418
+ </Text>
269419 </View>
270
- ))
271
- )}
420
+ ) : (
421
+ sortedSessions.map((session) => (
422
+ <View key={session.id} style={{ marginBottom: 6 }}>
423
+ {editingId === session.id ? (
424
+ <RenameEditor
425
+ name={session.name}
426
+ onConfirm={(name) =>
427
+ handleConfirmRename(session.id, name)
428
+ }
429
+ onCancel={() => setEditingId(null)}
430
+ />
431
+ ) : (
432
+ <SessionRow
433
+ session={session}
434
+ onSwitch={() => handleSwitch(session)}
435
+ onLongPress={() => handleStartRename(session)}
436
+ onDelete={() => handleRemove(session)}
437
+ />
438
+ )}
439
+ </View>
440
+ ))
441
+ )}
272442
273
- {/* Hint */}
274
- <Text
275
- style={{
276
- color: "#5A5A78",
277
- fontSize: 12,
278
- textAlign: "center",
279
- paddingVertical: 12,
280
- }}
281
- >
282
- Tap to switch — Long press to rename
283
- </Text>
284
- </ScrollView>
443
+ <Text
444
+ style={{
445
+ color: "#5A5A78",
446
+ fontSize: 11,
447
+ textAlign: "center",
448
+ paddingVertical: 10,
449
+ }}
450
+ >
451
+ Tap to switch — Long press to rename — Swipe left to remove
452
+ </Text>
453
+ </ScrollView>
454
+ </View>
285455 </View>
286
- </View>
456
+ </GestureHandlerRootView>
287457 </Modal>
288458 );
289459 }
components/chat/CommandBar.tsx
....@@ -1,14 +1,17 @@
11 import React, { useState } from "react";
2
-import { Pressable, Text, View, useWindowDimensions } from "react-native";
2
+import { Pressable, Text, View } from "react-native";
33 import * as Haptics from "expo-haptics";
4
+import { useTheme } from "../../contexts/ThemeContext";
45
56 interface CommandBarProps {
6
- onSessions: () => void;
77 onScreenshot: () => void;
8
- onHelp: () => void;
8
+ onNavigate: () => void;
9
+ onPhoto: () => void;
10
+ onClear: () => void;
911 }
1012
11
-export function CommandBar({ onSessions, onScreenshot, onHelp }: CommandBarProps) {
13
+export function CommandBar({ onScreenshot, onNavigate, onPhoto, onClear }: CommandBarProps) {
14
+ const { colors } = useTheme();
1215 return (
1316 <View
1417 style={{
....@@ -18,26 +21,30 @@
1821 gap: 8,
1922 }}
2023 >
21
- <CmdBtn icon="📋" label="Sessions" bg="#1A2744" border="#2E4A7A" onPress={onSessions} />
22
- <CmdBtn icon="📸" label="Screen" bg="#1A3A2A" border="#2E6A4A" onPress={onScreenshot} />
23
- <CmdBtn icon="❓" label="Help" bg="#3A1A2A" border="#6A2E4A" onPress={onHelp} />
24
+ <CmdBtn icon="📸" label="Screen" onPress={onScreenshot} colors={colors} />
25
+ <CmdBtn icon="🧭" label="Navigate" onPress={onNavigate} colors={colors} />
26
+ <CmdBtn icon="📎" label="Photo" onPress={onPhoto} colors={colors} />
27
+ <CmdBtn icon="🗑" label="Clear" onPress={onClear} colors={colors} />
2428 </View>
2529 );
2630 }
2731
2832 interface TextModeCommandBarProps {
29
- onSessions: () => void;
3033 onScreenshot: () => void;
3134 onNavigate: () => void;
35
+ onPhoto: () => void;
36
+ onHelp: () => void;
3237 onClear: () => void;
3338 }
3439
3540 export function TextModeCommandBar({
36
- onSessions,
3741 onScreenshot,
3842 onNavigate,
43
+ onPhoto,
44
+ onHelp,
3945 onClear,
4046 }: TextModeCommandBarProps) {
47
+ const { colors } = useTheme();
4148 return (
4249 <View
4350 style={{
....@@ -47,10 +54,11 @@
4754 gap: 8,
4855 }}
4956 >
50
- <CmdBtn icon="📋" label="Sessions" bg="#1A2744" border="#2E4A7A" onPress={onSessions} />
51
- <CmdBtn icon="📸" label="Screen" bg="#1A3A2A" border="#2E6A4A" onPress={onScreenshot} />
52
- <CmdBtn icon="🧭" label="Navigate" bg="#2A2A1A" border="#5A5A2E" onPress={onNavigate} />
53
- <CmdBtn icon="🗑" label="Clear" bg="#3A1A1A" border="#6A2E2E" onPress={onClear} />
57
+ <CmdBtn icon="📸" label="Screen" onPress={onScreenshot} colors={colors} />
58
+ <CmdBtn icon="🧭" label="Navigate" onPress={onNavigate} colors={colors} />
59
+ <CmdBtn icon="📎" label="Photo" onPress={onPhoto} colors={colors} />
60
+ <CmdBtn icon="❓" label="Help" onPress={onHelp} colors={colors} />
61
+ <CmdBtn icon="🗑" label="Clear" onPress={onClear} colors={colors} />
5462 </View>
5563 );
5664 }
....@@ -58,18 +66,15 @@
5866 function CmdBtn({
5967 icon,
6068 label,
61
- bg,
62
- border,
6369 onPress,
70
+ colors,
6471 }: {
6572 icon: string;
6673 label: string;
67
- bg: string;
68
- border: string;
6974 onPress: () => void;
75
+ colors: ReturnType<typeof useTheme>["colors"];
7076 }) {
7177 const [pressed, setPressed] = useState(false);
72
- const { width } = useWindowDimensions();
7378
7479 return (
7580 <View style={{ flex: 1 }}>
....@@ -87,13 +92,13 @@
8792 borderRadius: 16,
8893 alignItems: "center",
8994 justifyContent: "center",
90
- backgroundColor: pressed ? "#4A9EFF" : bg,
95
+ backgroundColor: pressed ? colors.accent : colors.bgTertiary,
9196 borderWidth: 1.5,
92
- borderColor: pressed ? "#4A9EFF" : border,
97
+ borderColor: pressed ? colors.accent : colors.border,
9398 }}
9499 >
95100 <Text style={{ fontSize: 26, marginBottom: 2 }}>{icon}</Text>
96
- <Text style={{ color: "#C8C8E0", fontSize: 13, fontWeight: "700" }}>
101
+ <Text style={{ color: colors.textSecondary, fontSize: 13, fontWeight: "700" }}>
97102 {label}
98103 </Text>
99104 </View>
components/chat/ImageCaptionModal.tsx
....@@ -0,0 +1,131 @@
1
+import React, { useEffect, useRef, useState } from "react";
2
+import {
3
+ Dimensions,
4
+ Image,
5
+ KeyboardAvoidingView,
6
+ Modal,
7
+ Platform,
8
+ Pressable,
9
+ Text,
10
+ TextInput,
11
+ View,
12
+} from "react-native";
13
+import { useTheme } from "../../contexts/ThemeContext";
14
+
15
+interface ImageCaptionModalProps {
16
+ visible: boolean;
17
+ imageUri: string;
18
+ onSend: (caption: string) => void;
19
+ onCancel: () => void;
20
+}
21
+
22
+export function ImageCaptionModal({ visible, imageUri, onSend, onCancel }: ImageCaptionModalProps) {
23
+ const { colors } = useTheme();
24
+ const [caption, setCaption] = useState("");
25
+ const inputRef = useRef<TextInput>(null);
26
+ const { width, height } = Dimensions.get("window");
27
+
28
+ useEffect(() => {
29
+ if (visible) {
30
+ setCaption("");
31
+ setTimeout(() => inputRef.current?.focus(), 300);
32
+ }
33
+ }, [visible]);
34
+
35
+ const handleSend = () => {
36
+ onSend(caption.trim());
37
+ setCaption("");
38
+ };
39
+
40
+ return (
41
+ <Modal visible={visible} animationType="slide" transparent={false} onRequestClose={onCancel}>
42
+ <View style={{ flex: 1, backgroundColor: "#000" }}>
43
+ <KeyboardAvoidingView
44
+ style={{ flex: 1 }}
45
+ behavior={Platform.OS === "ios" ? "padding" : undefined}
46
+ keyboardVerticalOffset={0}
47
+ >
48
+ {/* Top bar with cancel */}
49
+ <View
50
+ style={{
51
+ paddingTop: 54,
52
+ paddingHorizontal: 16,
53
+ paddingBottom: 12,
54
+ flexDirection: "row",
55
+ alignItems: "center",
56
+ justifyContent: "space-between",
57
+ }}
58
+ >
59
+ <Pressable
60
+ onPress={onCancel}
61
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
62
+ style={{
63
+ paddingHorizontal: 12,
64
+ paddingVertical: 6,
65
+ borderRadius: 16,
66
+ backgroundColor: "rgba(255,255,255,0.15)",
67
+ }}
68
+ >
69
+ <Text style={{ color: "#fff", fontSize: 16, fontWeight: "600" }}>Cancel</Text>
70
+ </Pressable>
71
+ </View>
72
+
73
+ {/* Image preview */}
74
+ <View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
75
+ <Image
76
+ source={{ uri: imageUri }}
77
+ style={{ width, height: height * 0.55 }}
78
+ resizeMode="contain"
79
+ />
80
+ </View>
81
+
82
+ {/* Caption input + send */}
83
+ <View
84
+ style={{
85
+ flexDirection: "row",
86
+ alignItems: "flex-end",
87
+ paddingHorizontal: 12,
88
+ paddingVertical: 10,
89
+ paddingBottom: 34,
90
+ gap: 8,
91
+ }}
92
+ >
93
+ <TextInput
94
+ ref={inputRef}
95
+ value={caption}
96
+ onChangeText={setCaption}
97
+ placeholder="Add a caption..."
98
+ placeholderTextColor="rgba(255,255,255,0.4)"
99
+ multiline
100
+ maxLength={2000}
101
+ style={{
102
+ flex: 1,
103
+ backgroundColor: "rgba(255,255,255,0.12)",
104
+ borderRadius: 20,
105
+ paddingHorizontal: 16,
106
+ paddingVertical: 10,
107
+ maxHeight: 100,
108
+ color: "#fff",
109
+ fontSize: 16,
110
+ }}
111
+ />
112
+ <Pressable
113
+ onPress={handleSend}
114
+ style={{
115
+ width: 44,
116
+ height: 44,
117
+ borderRadius: 22,
118
+ alignItems: "center",
119
+ justifyContent: "center",
120
+ backgroundColor: colors.accent,
121
+ marginBottom: 1,
122
+ }}
123
+ >
124
+ <Text style={{ fontSize: 20, fontWeight: "bold", color: "#fff" }}>{"\u2191"}</Text>
125
+ </Pressable>
126
+ </View>
127
+ </KeyboardAvoidingView>
128
+ </View>
129
+ </Modal>
130
+ );
131
+}
components/chat/ImageViewer.tsx
....@@ -0,0 +1,161 @@
1
+import React, { useCallback } from "react";
2
+import {
3
+ Alert,
4
+ Dimensions,
5
+ Image,
6
+ Modal,
7
+ Pressable,
8
+ ScrollView,
9
+ Text,
10
+ View,
11
+} from "react-native";
12
+import { cacheDirectory, writeAsStringAsync } from "expo-file-system/legacy";
13
+import * as Sharing from "expo-sharing";
14
+
15
+/** Apple-style share icon (square with upward arrow) */
16
+function ShareIcon({ size = 18, color = "#fff" }: { size?: number; color?: string }) {
17
+ const boxSize = size * 0.7;
18
+ const arrowWidth = 2;
19
+ return (
20
+ <View style={{ width: size, height: size, alignItems: "center", justifyContent: "flex-end" }}>
21
+ {/* Arrow shaft + head */}
22
+ <View
23
+ style={{
24
+ position: "absolute",
25
+ top: 0,
26
+ width: arrowWidth,
27
+ height: size * 0.65,
28
+ backgroundColor: color,
29
+ borderRadius: 1,
30
+ }}
31
+ />
32
+ <View
33
+ style={{
34
+ position: "absolute",
35
+ top: 0,
36
+ width: 0,
37
+ height: 0,
38
+ borderLeftWidth: size * 0.22,
39
+ borderRightWidth: size * 0.22,
40
+ borderBottomWidth: size * 0.25,
41
+ borderLeftColor: "transparent",
42
+ borderRightColor: "transparent",
43
+ borderBottomColor: color,
44
+ transform: [{ translateY: -size * 0.12 }],
45
+ }}
46
+ />
47
+ {/* Open box (3 sides) */}
48
+ <View
49
+ style={{
50
+ width: boxSize,
51
+ height: boxSize * 0.7,
52
+ borderWidth: arrowWidth,
53
+ borderTopWidth: 0,
54
+ borderColor: color,
55
+ borderRadius: 2,
56
+ }}
57
+ />
58
+ </View>
59
+ );
60
+}
61
+
62
+interface ImageViewerProps {
63
+ visible: boolean;
64
+ imageBase64: string;
65
+ onClose: () => void;
66
+}
67
+
68
+export function ImageViewer({ visible, imageBase64, onClose }: ImageViewerProps) {
69
+ const { width, height } = Dimensions.get("window");
70
+
71
+ const handleShare = useCallback(async () => {
72
+ try {
73
+ const fileUri = `${cacheDirectory}pailot-screenshot-${Date.now()}.png`;
74
+ await writeAsStringAsync(fileUri, imageBase64, {
75
+ encoding: "base64",
76
+ });
77
+ if (!(await Sharing.isAvailableAsync())) {
78
+ Alert.alert("Sharing not available on this device");
79
+ return;
80
+ }
81
+ await Sharing.shareAsync(fileUri, { mimeType: "image/png" });
82
+ } catch (err: any) {
83
+ if (err?.message?.includes("User did not share")) return;
84
+ Alert.alert("Share Error", err?.message ?? String(err));
85
+ }
86
+ }, [imageBase64]);
87
+
88
+ return (
89
+ <Modal
90
+ visible={visible}
91
+ transparent
92
+ animationType="fade"
93
+ onRequestClose={onClose}
94
+ >
95
+ <View style={{ flex: 1, backgroundColor: "rgba(0,0,0,0.95)" }}>
96
+ {/* Top bar: share + close */}
97
+ <View
98
+ style={{
99
+ position: "absolute",
100
+ top: 60,
101
+ right: 20,
102
+ zIndex: 10,
103
+ flexDirection: "row",
104
+ gap: 12,
105
+ }}
106
+ >
107
+ <Pressable
108
+ onPress={handleShare}
109
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
110
+ style={{
111
+ width: 40,
112
+ height: 40,
113
+ borderRadius: 20,
114
+ backgroundColor: "rgba(255,255,255,0.15)",
115
+ alignItems: "center",
116
+ justifyContent: "center",
117
+ }}
118
+ >
119
+ <ShareIcon size={20} color="#fff" />
120
+ </Pressable>
121
+ <Pressable
122
+ onPress={onClose}
123
+ hitSlop={{ top: 12, bottom: 12, left: 12, right: 12 }}
124
+ style={{
125
+ width: 40,
126
+ height: 40,
127
+ borderRadius: 20,
128
+ backgroundColor: "rgba(255,255,255,0.15)",
129
+ alignItems: "center",
130
+ justifyContent: "center",
131
+ }}
132
+ >
133
+ <Text style={{ color: "#fff", fontSize: 20, fontWeight: "600" }}>
134
+ ✕
135
+ </Text>
136
+ </Pressable>
137
+ </View>
138
+
139
+ {/* Zoomable image */}
140
+ <ScrollView
141
+ maximumZoomScale={5}
142
+ minimumZoomScale={1}
143
+ centerContent
144
+ contentContainerStyle={{
145
+ flex: 1,
146
+ justifyContent: "center",
147
+ alignItems: "center",
148
+ }}
149
+ showsVerticalScrollIndicator={false}
150
+ showsHorizontalScrollIndicator={false}
151
+ >
152
+ <Image
153
+ source={{ uri: `data:image/png;base64,${imageBase64}` }}
154
+ style={{ width, height: height * 0.85 }}
155
+ resizeMode="contain"
156
+ />
157
+ </ScrollView>
158
+ </View>
159
+ </Modal>
160
+ );
161
+}
components/chat/InputBar.tsx
....@@ -8,22 +8,30 @@
88 } from "react-native";
99 import * as Haptics from "expo-haptics";
1010 import { VoiceButton } from "./VoiceButton";
11
+import { useTheme } from "../../contexts/ThemeContext";
1112
1213 interface InputBarProps {
1314 onSendText: (text: string) => void;
15
+ onVoiceRecorded: (uri: string) => void;
1416 onReplay: () => void;
1517 isTextMode: boolean;
1618 onToggleMode: () => void;
19
+ audioPlaying?: boolean;
1720 }
1821
1922 export function InputBar({
2023 onSendText,
24
+ onVoiceRecorded,
2125 onReplay,
2226 isTextMode,
2327 onToggleMode,
28
+ audioPlaying = false,
2429 }: InputBarProps) {
2530 const [text, setText] = useState("");
2631 const inputRef = useRef<TextInput>(null);
32
+ const { colors } = useTheme();
33
+
34
+ const canSend = !!text.trim();
2735
2836 const handleSend = useCallback(() => {
2937 const trimmed = text.trim();
....@@ -43,11 +51,11 @@
4351 paddingVertical: 10,
4452 paddingBottom: 6,
4553 borderTopWidth: 1,
46
- borderTopColor: "#2E2E45",
54
+ borderTopColor: colors.border,
4755 alignItems: "center",
4856 }}
4957 >
50
- {/* Replay last message */}
58
+ {/* Replay / Stop */}
5159 <Pressable
5260 onPress={() => {
5361 Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
....@@ -61,19 +69,21 @@
6169 borderRadius: 34,
6270 alignItems: "center",
6371 justifyContent: "center",
64
- backgroundColor: "#1A2E1A",
72
+ backgroundColor: colors.bgTertiary,
6573 borderWidth: 1.5,
66
- borderColor: "#3A6A3A",
74
+ borderColor: colors.border,
6775 }}
6876 >
69
- <Text style={{ fontSize: 24 }}>▶</Text>
70
- <Text style={{ color: "#8ABF8A", fontSize: 10, marginTop: 1, fontWeight: "600" }}>Replay</Text>
77
+ <Text style={{ fontSize: 24 }}>{audioPlaying ? "\u23F8" : "\u25B6"}</Text>
78
+ <Text style={{ color: colors.textSecondary, fontSize: 10, marginTop: 1, fontWeight: "600" }}>
79
+ {audioPlaying ? "Stop" : "Replay"}
80
+ </Text>
7181 </View>
7282 </Pressable>
7383
74
- {/* Talk button — center, biggest */}
84
+ {/* Talk button */}
7585 <View style={{ flex: 1, alignItems: "center" }}>
76
- <VoiceButton onTranscript={onSendText} />
86
+ <VoiceButton onVoiceRecorded={onVoiceRecorded} />
7787 </View>
7888
7989 {/* Text mode toggle */}
....@@ -91,12 +101,12 @@
91101 borderRadius: 34,
92102 alignItems: "center",
93103 justifyContent: "center",
94
- backgroundColor: "#1A1A3E",
104
+ backgroundColor: colors.bgTertiary,
95105 borderWidth: 1.5,
96
- borderColor: "#3A3A7A",
106
+ borderColor: colors.border,
97107 }}
98108 >
99
- <Text style={{ fontSize: 22, color: "#9898D0", fontWeight: "700" }}>Aa</Text>
109
+ <Text style={{ fontSize: 22, color: colors.textSecondary, fontWeight: "700" }}>Aa</Text>
100110 </View>
101111 </Pressable>
102112 </View>
....@@ -112,7 +122,7 @@
112122 paddingHorizontal: 12,
113123 paddingVertical: 8,
114124 borderTopWidth: 1,
115
- borderTopColor: "#2E2E45",
125
+ borderTopColor: colors.border,
116126 alignItems: "flex-end",
117127 }}
118128 >
....@@ -129,11 +139,11 @@
129139 borderRadius: 20,
130140 alignItems: "center",
131141 justifyContent: "center",
132
- backgroundColor: "#1E1E2E",
142
+ backgroundColor: colors.bgTertiary,
133143 marginBottom: 2,
134144 }}
135145 >
136
- <Text style={{ fontSize: 20 }}>🎤</Text>
146
+ <Text style={{ fontSize: 20 }}>{"\uD83C\uDFA4"}</Text>
137147 </Pressable>
138148
139149 {/* Text input */}
....@@ -142,7 +152,7 @@
142152 value={text}
143153 onChangeText={setText}
144154 placeholder="Message PAI..."
145
- placeholderTextColor="#5A5A78"
155
+ placeholderTextColor={colors.textMuted}
146156 multiline
147157 maxLength={2000}
148158 onSubmitEditing={handleSend}
....@@ -150,12 +160,12 @@
150160 blurOnSubmit
151161 style={{
152162 flex: 1,
153
- backgroundColor: "#1E1E2E",
163
+ backgroundColor: colors.bgTertiary,
154164 borderRadius: 20,
155165 paddingHorizontal: 16,
156166 paddingVertical: 10,
157167 maxHeight: 120,
158
- color: "#E8E8F0",
168
+ color: colors.text,
159169 fontSize: 16,
160170 }}
161171 />
....@@ -163,7 +173,7 @@
163173 {/* Send button */}
164174 <Pressable
165175 onPress={handleSend}
166
- disabled={!text.trim()}
176
+ disabled={!canSend}
167177 style={{
168178 width: 40,
169179 height: 40,
....@@ -171,17 +181,17 @@
171181 alignItems: "center",
172182 justifyContent: "center",
173183 marginBottom: 2,
174
- backgroundColor: text.trim() ? "#4A9EFF" : "#1E1E2E",
184
+ backgroundColor: canSend ? colors.accent : colors.bgTertiary,
175185 }}
176186 >
177187 <Text
178188 style={{
179189 fontSize: 18,
180190 fontWeight: "bold",
181
- color: text.trim() ? "#FFFFFF" : "#5A5A78",
191
+ color: canSend ? "#FFFFFF" : colors.textMuted,
182192 }}
183193 >
184
- ↑
194
+ {"\u2191"}
185195 </Text>
186196 </Pressable>
187197 </View>
components/chat/MessageBubble.tsx
....@@ -1,7 +1,9 @@
1
-import React, { useCallback, useState } from "react";
1
+import React, { useCallback, useEffect, useState } from "react";
22 import { Image, Pressable, Text, View } from "react-native";
33 import { Message } from "../../types";
4
-import { playAudio, stopPlayback } from "../../services/audio";
4
+import { playAudio, stopPlayback, onPlayingChange } from "../../services/audio";
5
+import { ImageViewer } from "./ImageViewer";
6
+import { useTheme } from "../../contexts/ThemeContext";
57
68 interface MessageBubbleProps {
79 message: Message;
....@@ -22,6 +24,14 @@
2224
2325 export function MessageBubble({ message }: MessageBubbleProps) {
2426 const [isPlaying, setIsPlaying] = useState(false);
27
+ const [showViewer, setShowViewer] = useState(false);
28
+ const { colors, isDark } = useTheme();
29
+
30
+ useEffect(() => {
31
+ return onPlayingChange((playing) => {
32
+ if (!playing) setIsPlaying(false);
33
+ });
34
+ }, []);
2535
2636 const isUser = message.role === "user";
2737 const isSystem = message.role === "system";
....@@ -40,40 +50,56 @@
4050
4151 if (isSystem) {
4252 return (
43
- <View className="items-center my-1 px-4">
44
- <Text className="text-pai-text-muted text-xs">{message.content}</Text>
53
+ <View style={{ alignItems: "center", marginVertical: 4, paddingHorizontal: 16 }}>
54
+ <Text style={{ color: colors.textMuted, fontSize: 12 }}>{message.content}</Text>
4555 </View>
4656 );
4757 }
4858
59
+ const bubbleBg = isUser
60
+ ? colors.accent
61
+ : isDark ? "#252538" : colors.bgSecondary;
62
+ const bubbleRadius = isUser
63
+ ? { borderTopRightRadius: 4 }
64
+ : { borderTopLeftRadius: 4 };
65
+
4966 return (
5067 <View
51
- className={`flex-row my-1 px-3 ${isUser ? "justify-end" : "justify-start"}`}
68
+ style={{
69
+ flexDirection: "row",
70
+ marginVertical: 4,
71
+ paddingHorizontal: 12,
72
+ justifyContent: isUser ? "flex-end" : "flex-start",
73
+ }}
5274 >
5375 <View
54
- className={`max-w-[78%] rounded-2xl px-4 py-3 ${
55
- isUser
56
- ? "bg-pai-accent rounded-tr-sm"
57
- : "bg-pai-surface rounded-tl-sm"
58
- }`}
76
+ style={{
77
+ maxWidth: "78%",
78
+ borderRadius: 16,
79
+ paddingHorizontal: 16,
80
+ paddingVertical: 12,
81
+ backgroundColor: bubbleBg,
82
+ ...bubbleRadius,
83
+ }}
5984 >
6085 {message.type === "image" && message.imageBase64 ? (
61
- /* Image message */
6286 <View>
63
- <Image
64
- source={{ uri: `data:image/png;base64,${message.imageBase64}` }}
65
- style={{
66
- width: 260,
67
- height: 180,
68
- borderRadius: 10,
69
- backgroundColor: "#14141F",
70
- }}
71
- resizeMode="contain"
72
- />
87
+ <Pressable onPress={() => setShowViewer(true)}>
88
+ <Image
89
+ source={{ uri: `data:image/png;base64,${message.imageBase64}` }}
90
+ style={{
91
+ width: 260,
92
+ height: 180,
93
+ borderRadius: 10,
94
+ backgroundColor: colors.bgTertiary,
95
+ }}
96
+ resizeMode="contain"
97
+ />
98
+ </Pressable>
7399 {message.content ? (
74100 <Text
75101 style={{
76
- color: isUser ? "#FFF" : "#9898B0",
102
+ color: isUser ? "#FFF" : colors.textSecondary,
77103 fontSize: 12,
78104 marginTop: 4,
79105 }}
....@@ -81,74 +107,94 @@
81107 {message.content}
82108 </Text>
83109 ) : null}
110
+ <ImageViewer
111
+ visible={showViewer}
112
+ imageBase64={message.imageBase64}
113
+ onClose={() => setShowViewer(false)}
114
+ />
84115 </View>
85116 ) : message.type === "voice" ? (
86117 <Pressable
87118 onPress={handleVoicePress}
88
- className="flex-row items-center gap-3"
119
+ style={{ flexDirection: "row", alignItems: "center", gap: 12 }}
89120 >
90
- {/* Play/pause icon */}
91121 <View
92
- className={`w-9 h-9 rounded-full items-center justify-center ${
93
- isPlaying ? "bg-pai-voice" : isUser ? "bg-white/20" : "bg-pai-border"
94
- }`}
122
+ style={{
123
+ width: 36,
124
+ height: 36,
125
+ borderRadius: 18,
126
+ alignItems: "center",
127
+ justifyContent: "center",
128
+ backgroundColor: isPlaying
129
+ ? "#FF9F43"
130
+ : isUser
131
+ ? "rgba(255,255,255,0.2)"
132
+ : colors.border,
133
+ }}
95134 >
96
- <Text
97
- className={`text-base ${isUser ? "text-white" : "text-pai-text"}`}
98
- >
99
- {isPlaying ? "⏸" : "▶"}
135
+ <Text style={{ fontSize: 14, color: isUser ? "#FFF" : colors.text }}>
136
+ {isPlaying ? "\u23F8" : "\u25B6"}
100137 </Text>
101138 </View>
102139
103
- {/* Waveform placeholder */}
104
- <View className="flex-1 flex-row items-center gap-px h-8">
140
+ <View style={{ flex: 1, flexDirection: "row", alignItems: "center", gap: 1, height: 32 }}>
105141 {Array.from({ length: 20 }).map((_, i) => (
106142 <View
107143 key={i}
108
- className={`flex-1 rounded-full ${
109
- isPlaying && i < 10
110
- ? "bg-pai-voice"
111
- : isUser
112
- ? "bg-white/50"
113
- : "bg-pai-text-muted"
114
- }`}
115144 style={{
145
+ flex: 1,
146
+ borderRadius: 2,
147
+ backgroundColor: isPlaying && i < 10
148
+ ? "#FF9F43"
149
+ : isUser
150
+ ? "rgba(255,255,255,0.5)"
151
+ : colors.textMuted,
116152 height: `${20 + Math.sin(i * 0.8) * 60}%`,
117153 }}
118154 />
119155 ))}
120156 </View>
121157
122
- {/* Duration */}
123158 <Text
124
- className={`text-xs ${
125
- isUser ? "text-white/80" : "text-pai-text-secondary"
126
- }`}
159
+ style={{
160
+ fontSize: 11,
161
+ color: isUser ? "rgba(255,255,255,0.8)" : colors.textSecondary,
162
+ }}
127163 >
128164 {formatDuration(message.duration)}
129165 </Text>
130166 </Pressable>
131167 ) : (
132168 <Text
133
- className={`text-base leading-6 ${
134
- isUser ? "text-white" : "text-pai-text"
135
- }`}
169
+ style={{
170
+ fontSize: 16,
171
+ lineHeight: 24,
172
+ color: isUser ? "#FFF" : colors.text,
173
+ }}
136174 >
137175 {message.content}
138176 </Text>
139177 )}
140178
141
- {/* Timestamp + status */}
142
- <View className={`flex-row items-center mt-1 gap-1 ${isUser ? "justify-end" : "justify-start"}`}>
179
+ <View
180
+ style={{
181
+ flexDirection: "row",
182
+ alignItems: "center",
183
+ marginTop: 4,
184
+ gap: 4,
185
+ justifyContent: isUser ? "flex-end" : "flex-start",
186
+ }}
187
+ >
143188 <Text
144
- className={`text-2xs ${
145
- isUser ? "text-white/60" : "text-pai-text-muted"
146
- }`}
189
+ style={{
190
+ fontSize: 10,
191
+ color: isUser ? "rgba(255,255,255,0.6)" : colors.textMuted,
192
+ }}
147193 >
148194 {formatTime(message.timestamp)}
149195 </Text>
150196 {isUser && message.status === "error" && (
151
- <Text className="text-2xs text-pai-error"> !</Text>
197
+ <Text style={{ fontSize: 10, color: colors.danger }}> !</Text>
152198 )}
153199 </View>
154200 </View>
components/chat/VoiceButton.tsx
....@@ -1,59 +1,34 @@
1
-import React, { useCallback, useEffect, useRef, useState } from "react";
1
+import React, { useCallback, useRef, useState } from "react";
22 import { Animated, Pressable, Text, View } from "react-native";
33 import * as Haptics from "expo-haptics";
44 import {
5
- ExpoSpeechRecognitionModule,
6
- useSpeechRecognitionEvent,
7
-} from "expo-speech-recognition";
5
+ useAudioRecorder,
6
+ RecordingPresets,
7
+ requestRecordingPermissionsAsync,
8
+ setAudioModeAsync,
9
+} from "expo-audio";
10
+import { stopPlayback } from "../../services/audio";
811
912 interface VoiceButtonProps {
10
- onTranscript: (text: string) => void;
13
+ onVoiceRecorded: (uri: string) => void;
1114 }
1215
1316 const VOICE_BUTTON_SIZE = 72;
1417
1518 /**
16
- * Tap-to-toggle voice button using on-device speech recognition.
17
- * - Tap once: start listening
18
- * - Tap again: stop and send transcript
19
- * - Long-press while listening: cancel (discard)
19
+ * Tap-to-toggle voice button using expo-audio recording.
20
+ * Records audio and returns the file URI for the caller to send.
21
+ * - Tap once: start recording
22
+ * - Tap again: stop and send
23
+ * - Long-press while recording: cancel (discard)
2024 */
21
-export function VoiceButton({ onTranscript }: VoiceButtonProps) {
22
- const [isListening, setIsListening] = useState(false);
23
- const [transcript, setTranscript] = useState("");
25
+export function VoiceButton({ onVoiceRecorded }: VoiceButtonProps) {
26
+ const [isRecording, setIsRecording] = useState(false);
2427 const pulseAnim = useRef(new Animated.Value(1)).current;
2528 const glowAnim = useRef(new Animated.Value(0)).current;
2629 const pulseLoop = useRef<Animated.CompositeAnimation | null>(null);
27
- const cancelledRef = useRef(false);
2830
29
- // Speech recognition events
30
- useSpeechRecognitionEvent("start", () => {
31
- setIsListening(true);
32
- });
33
-
34
- useSpeechRecognitionEvent("end", () => {
35
- setIsListening(false);
36
- stopPulse();
37
-
38
- // Send transcript if we have one and weren't cancelled
39
- if (!cancelledRef.current && transcript.trim()) {
40
- onTranscript(transcript.trim());
41
- }
42
- setTranscript("");
43
- cancelledRef.current = false;
44
- });
45
-
46
- useSpeechRecognitionEvent("result", (event) => {
47
- const text = event.results[0]?.transcript ?? "";
48
- setTranscript(text);
49
- });
50
-
51
- useSpeechRecognitionEvent("error", (event) => {
52
- console.error("Speech recognition error:", event.error, event.message);
53
- setIsListening(false);
54
- stopPulse();
55
- setTranscript("");
56
- });
31
+ const recorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
5732
5833 const startPulse = useCallback(() => {
5934 pulseLoop.current = Animated.loop(
....@@ -88,49 +63,73 @@
8863 }).start();
8964 }, [pulseAnim, glowAnim]);
9065
91
- const startListening = useCallback(async () => {
92
- const result = await ExpoSpeechRecognitionModule.requestPermissionsAsync();
93
- if (!result.granted) return;
66
+ const startRecording = useCallback(async () => {
67
+ try {
68
+ await stopPlayback();
9469
95
- cancelledRef.current = false;
96
- setTranscript("");
97
- startPulse();
70
+ const { granted } = await requestRecordingPermissionsAsync();
71
+ if (!granted) return;
9872
99
- ExpoSpeechRecognitionModule.start({
100
- lang: "en-US",
101
- interimResults: true,
102
- continuous: true,
103
- });
104
- }, [startPulse]);
73
+ await setAudioModeAsync({
74
+ allowsRecording: true,
75
+ playsInSilentMode: true,
76
+ });
10577
106
- const stopAndSend = useCallback(() => {
78
+ startPulse();
79
+ await recorder.prepareToRecordAsync();
80
+ recorder.record();
81
+ setIsRecording(true);
82
+ } catch (err) {
83
+ console.error("Failed to start recording:", err);
84
+ stopPulse();
85
+ setIsRecording(false);
86
+ }
87
+ }, [recorder, startPulse, stopPulse]);
88
+
89
+ const stopAndSend = useCallback(async () => {
10790 stopPulse();
108
- cancelledRef.current = false;
109
- ExpoSpeechRecognitionModule.stop();
110
- }, [stopPulse]);
91
+ setIsRecording(false);
92
+ try {
93
+ await recorder.stop();
94
+ // Reset audio mode for playback
95
+ await setAudioModeAsync({
96
+ allowsRecording: false,
97
+ playsInSilentMode: true,
98
+ });
99
+ const uri = recorder.uri;
100
+ if (uri) {
101
+ onVoiceRecorded(uri);
102
+ }
103
+ } catch (err) {
104
+ console.error("Failed to stop recording:", err);
105
+ }
106
+ }, [recorder, stopPulse, onVoiceRecorded]);
111107
112
- const cancelListening = useCallback(() => {
108
+ const cancelRecording = useCallback(async () => {
113109 Haptics.notificationAsync(Haptics.NotificationFeedbackType.Warning);
114110 stopPulse();
115
- cancelledRef.current = true;
116
- setTranscript("");
117
- ExpoSpeechRecognitionModule.abort();
118
- }, [stopPulse]);
111
+ setIsRecording(false);
112
+ try {
113
+ await recorder.stop();
114
+ } catch {
115
+ // ignore
116
+ }
117
+ }, [recorder, stopPulse]);
119118
120119 const handleTap = useCallback(async () => {
121120 Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
122
- if (isListening) {
123
- stopAndSend();
121
+ if (isRecording) {
122
+ await stopAndSend();
124123 } else {
125
- await startListening();
124
+ await startRecording();
126125 }
127
- }, [isListening, stopAndSend, startListening]);
126
+ }, [isRecording, stopAndSend, startRecording]);
128127
129128 const handleLongPress = useCallback(() => {
130
- if (isListening) {
131
- cancelListening();
129
+ if (isRecording) {
130
+ cancelRecording();
132131 }
133
- }, [isListening, cancelListening]);
132
+ }, [isRecording, cancelRecording]);
134133
135134 return (
136135 <View style={{ alignItems: "center", justifyContent: "center" }}>
....@@ -141,7 +140,7 @@
141140 width: VOICE_BUTTON_SIZE + 24,
142141 height: VOICE_BUTTON_SIZE + 24,
143142 borderRadius: (VOICE_BUTTON_SIZE + 24) / 2,
144
- backgroundColor: isListening ? "rgba(255, 159, 67, 0.12)" : "transparent",
143
+ backgroundColor: isRecording ? "rgba(255, 159, 67, 0.12)" : "transparent",
145144 transform: [{ scale: pulseAnim }],
146145 opacity: glowAnim,
147146 }}
....@@ -158,35 +157,30 @@
158157 width: VOICE_BUTTON_SIZE,
159158 height: VOICE_BUTTON_SIZE,
160159 borderRadius: VOICE_BUTTON_SIZE / 2,
161
- backgroundColor: isListening ? "#FF9F43" : "#4A9EFF",
160
+ backgroundColor: isRecording ? "#FF9F43" : "#4A9EFF",
162161 alignItems: "center",
163162 justifyContent: "center",
164
- shadowColor: isListening ? "#FF9F43" : "#4A9EFF",
163
+ shadowColor: isRecording ? "#FF9F43" : "#4A9EFF",
165164 shadowOffset: { width: 0, height: 4 },
166165 shadowOpacity: 0.4,
167166 shadowRadius: 12,
168167 elevation: 8,
169168 }}
170169 >
171
- <Text style={{ fontSize: 28 }}>{isListening ? "⏹" : "🎤"}</Text>
170
+ <Text style={{ fontSize: 28 }}>{isRecording ? "⏹" : "🎤"}</Text>
172171 </View>
173172 </Pressable>
174173
175
- {/* Label / transcript preview */}
174
+ {/* Label */}
176175 <Text
177176 style={{
178
- color: isListening ? "#FF9F43" : "#5A5A78",
177
+ color: isRecording ? "#FF9F43" : "#5A5A78",
179178 fontSize: 11,
180179 marginTop: 4,
181
- fontWeight: isListening ? "600" : "400",
182
- maxWidth: 200,
183
- textAlign: "center",
180
+ fontWeight: isRecording ? "600" : "400",
184181 }}
185
- numberOfLines={2}
186182 >
187
- {isListening
188
- ? transcript || "Listening..."
189
- : "Tap to talk"}
183
+ {isRecording ? "Recording..." : "Tap to talk"}
190184 </Text>
191185 </View>
192186 );
components/ui/IconButton.tsx
....@@ -1,28 +1,28 @@
11 import React from "react";
22 import { Pressable, Text, ViewStyle } from "react-native";
3
+import { useTheme } from "../../contexts/ThemeContext";
34
45 interface IconButtonProps {
56 onPress: () => void;
67 label: string;
78 size?: number;
89 style?: ViewStyle;
9
- className?: string;
1010 }
1111
1212 export function IconButton({
1313 onPress,
1414 label,
1515 size = 24,
16
- className = "",
1716 }: IconButtonProps) {
17
+ const { colors } = useTheme();
18
+
1819 return (
1920 <Pressable
2021 onPress={onPress}
21
- className={`items-center justify-center ${className}`}
22
- style={{ width: size, height: size }}
22
+ style={{ width: size, height: size, alignItems: "center", justifyContent: "center" }}
2323 hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
2424 >
25
- <Text className="text-pai-text-secondary" style={{ fontSize: size * 0.7 }}>
25
+ <Text style={{ color: colors.textSecondary, fontSize: size * 0.7 }}>
2626 {label}
2727 </Text>
2828 </Pressable>
components/ui/StatusDot.tsx
....@@ -8,17 +8,19 @@
88 }
99
1010 export function StatusDot({ status, size = 10 }: StatusDotProps) {
11
- const colorClass =
11
+ const color =
1212 status === "connected"
13
- ? "bg-pai-success"
13
+ ? "#22c55e"
14
+ : status === "compacting"
15
+ ? "#3b82f6"
1416 : status === "connecting"
15
- ? "bg-pai-warning"
16
- : "bg-pai-error";
17
+ ? "#eab308"
18
+ : "#ef4444";
1719
1820 return (
1921 <View
20
- className={`rounded-full ${colorClass}`}
21
- style={{ width: size, height: size }}
22
+ className="rounded-full"
23
+ style={{ width: size, height: size, backgroundColor: color }}
2224 />
2325 );
2426 }
contexts/ChatContext.tsx
....@@ -8,23 +8,115 @@
88 } from "react";
99 import { Message, WsIncoming, WsSession } from "../types";
1010 import { useConnection } from "./ConnectionContext";
11
-import { playAudio, encodeAudioToBase64 } from "../services/audio";
11
+import { playAudio, encodeAudioToBase64, saveBase64Audio } from "../services/audio";
12
+import { requestNotificationPermissions, notifyIncomingMessage } from "../services/notifications";
1213
1314 function generateId(): string {
1415 return Date.now().toString(36) + Math.random().toString(36).slice(2);
1516 }
1617
18
+// --- Message persistence ---
19
+// Lazily import expo-file-system/legacy so a missing native module doesn't crash the app.
20
+
21
+let _fsReady: Promise<typeof import("expo-file-system/legacy")> | null = null;
22
+function getFs() {
23
+ if (!_fsReady) _fsReady = import("expo-file-system/legacy");
24
+ return _fsReady;
25
+}
26
+
27
+const MESSAGES_DIR = "pailot-messages";
28
+
29
+/** Strip heavy fields (base64 images, audio URIs) before persisting. */
30
+function lightMessage(m: Message): Message {
31
+ const light = { ...m };
32
+ if (light.imageBase64) light.imageBase64 = undefined;
33
+ if (light.audioUri) light.audioUri = undefined;
34
+ return light;
35
+}
36
+
37
+async function persistMessages(map: Record<string, Message[]>): Promise<void> {
38
+ try {
39
+ const fs = await getFs();
40
+ const dir = `${fs.documentDirectory}${MESSAGES_DIR}/`;
41
+ const dirInfo = await fs.getInfoAsync(dir);
42
+ if (!dirInfo.exists) await fs.makeDirectoryAsync(dir, { intermediates: true });
43
+ // Save each session's messages
44
+ for (const [sessionId, msgs] of Object.entries(map)) {
45
+ if (msgs.length === 0) continue;
46
+ const light = msgs.map(lightMessage);
47
+ await fs.writeAsStringAsync(`${dir}${sessionId}.json`, JSON.stringify(light));
48
+ }
49
+ } catch {
50
+ // Persistence is best-effort
51
+ }
52
+}
53
+
54
+async function loadMessages(): Promise<Record<string, Message[]>> {
55
+ try {
56
+ const fs = await getFs();
57
+ const dir = `${fs.documentDirectory}${MESSAGES_DIR}/`;
58
+ const dirInfo = await fs.getInfoAsync(dir);
59
+ if (!dirInfo.exists) return {};
60
+ const files = await fs.readDirectoryAsync(dir);
61
+ const result: Record<string, Message[]> = {};
62
+ for (const file of files) {
63
+ if (!file.endsWith(".json")) continue;
64
+ const sessionId = file.replace(".json", "");
65
+ const content = await fs.readAsStringAsync(`${dir}${file}`);
66
+ result[sessionId] = JSON.parse(content) as Message[];
67
+ }
68
+ return result;
69
+ } catch {
70
+ return {};
71
+ }
72
+}
73
+
74
+async function deletePersistedSession(sessionId: string): Promise<void> {
75
+ try {
76
+ const fs = await getFs();
77
+ const path = `${fs.documentDirectory}${MESSAGES_DIR}/${sessionId}.json`;
78
+ const info = await fs.getInfoAsync(path);
79
+ if (info.exists) await fs.deleteAsync(path);
80
+ } catch {
81
+ // Best-effort
82
+ }
83
+}
84
+
85
+async function clearPersistedMessages(sessionId: string): Promise<void> {
86
+ try {
87
+ const fs = await getFs();
88
+ await fs.writeAsStringAsync(
89
+ `${fs.documentDirectory}${MESSAGES_DIR}/${sessionId}.json`,
90
+ "[]"
91
+ );
92
+ } catch {
93
+ // Best-effort
94
+ }
95
+}
96
+
97
+// --- Debounced save ---
98
+let saveTimer: ReturnType<typeof setTimeout> | null = null;
99
+function debouncedSave(map: Record<string, Message[]>): void {
100
+ if (saveTimer) clearTimeout(saveTimer);
101
+ saveTimer = setTimeout(() => persistMessages(map), 1000);
102
+}
103
+
104
+// --- Context ---
105
+
17106 interface ChatContextValue {
18107 messages: Message[];
19108 sendTextMessage: (text: string) => void;
20109 sendVoiceMessage: (audioUri: string, durationMs?: number) => void;
110
+ sendImageMessage: (imageBase64: string, caption: string, mimeType: string) => void;
21111 clearMessages: () => void;
22
- // Session management
23112 sessions: WsSession[];
113
+ activeSessionId: string | null;
24114 requestSessions: () => void;
25115 switchSession: (sessionId: string) => void;
26116 renameSession: (sessionId: string, name: string) => void;
27
- // Screenshot / navigation
117
+ removeSession: (sessionId: string) => void;
118
+ createSession: () => void;
119
+ unreadCounts: Record<string, number>;
28120 latestScreenshot: string | null;
29121 requestScreenshot: () => void;
30122 sendNavKey: (key: string) => void;
....@@ -33,18 +125,104 @@
33125 const ChatContext = createContext<ChatContextValue | null>(null);
34126
35127 export function ChatProvider({ children }: { children: React.ReactNode }) {
36
- const [messages, setMessages] = useState<Message[]>([]);
37128 const [sessions, setSessions] = useState<WsSession[]>([]);
129
+ const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
38130 const [latestScreenshot, setLatestScreenshot] = useState<string | null>(null);
131
+ const needsSync = useRef(true);
132
+
133
+ // Per-session message storage
134
+ const messagesMapRef = useRef<Record<string, Message[]>>({});
135
+ // Messages for the active session (drives re-renders)
136
+ const [messages, setMessages] = useState<Message[]>([]);
137
+ // Unread counts for non-active sessions
138
+ const [unreadCounts, setUnreadCounts] = useState<Record<string, number>>({});
139
+
39140 const {
141
+ status,
40142 sendTextMessage: wsSend,
41143 sendVoiceMessage: wsVoice,
144
+ sendImageMessage: wsImageSend,
42145 sendCommand,
43146 onMessageReceived,
44147 } = useConnection();
45148
46
- const addMessage = useCallback((msg: Message) => {
47
- setMessages((prev) => [...prev, msg]);
149
+ // Restore persisted messages on mount + request notification permissions
150
+ useEffect(() => {
151
+ loadMessages().then((loaded) => {
152
+ if (Object.keys(loaded).length > 0) {
153
+ messagesMapRef.current = loaded;
154
+ }
155
+ });
156
+ requestNotificationPermissions();
157
+ }, []);
158
+
159
+ // Derive active session ID from sessions list when it arrives
160
+ const syncActiveFromSessions = useCallback((incoming: WsSession[]) => {
161
+ const active = incoming.find((s) => s.isActive);
162
+ if (active) {
163
+ setActiveSessionId((prev) => {
164
+ if (prev !== active.id) {
165
+ if (prev) {
166
+ messagesMapRef.current[prev] = messages;
167
+ }
168
+ const stored = messagesMapRef.current[active.id] ?? [];
169
+ setMessages(stored);
170
+ setUnreadCounts((u) => {
171
+ if (!u[active.id]) return u;
172
+ const next = { ...u };
173
+ delete next[active.id];
174
+ return next;
175
+ });
176
+ }
177
+ return active.id;
178
+ });
179
+ }
180
+ }, [messages]);
181
+
182
+ // On connect: ask gateway to detect the focused iTerm2 session and sync
183
+ useEffect(() => {
184
+ if (status === "connected") {
185
+ needsSync.current = true;
186
+ sendCommand("sync");
187
+ }
188
+ }, [status, sendCommand]);
189
+
190
+ // Helper: add a message to the active session
191
+ const addMessageToActive = useCallback((msg: Message) => {
192
+ setMessages((prev) => {
193
+ const next = [...prev, msg];
194
+ setActiveSessionId((id) => {
195
+ if (id) {
196
+ messagesMapRef.current[id] = next;
197
+ debouncedSave(messagesMapRef.current);
198
+ }
199
+ return id;
200
+ });
201
+ return next;
202
+ });
203
+ }, []);
204
+
205
+ // Helper: add a message to a specific session (may not be active)
206
+ const addMessageToSession = useCallback((sessionId: string, msg: Message) => {
207
+ setActiveSessionId((currentActive) => {
208
+ if (sessionId === currentActive) {
209
+ setMessages((prev) => {
210
+ const next = [...prev, msg];
211
+ messagesMapRef.current[sessionId] = next;
212
+ debouncedSave(messagesMapRef.current);
213
+ return next;
214
+ });
215
+ } else {
216
+ const existing = messagesMapRef.current[sessionId] ?? [];
217
+ messagesMapRef.current[sessionId] = [...existing, msg];
218
+ debouncedSave(messagesMapRef.current);
219
+ setUnreadCounts((u) => ({
220
+ ...u,
221
+ [sessionId]: (u[sessionId] ?? 0) + 1,
222
+ }));
223
+ }
224
+ return currentActive;
225
+ });
48226 }, []);
49227
50228 const updateMessageStatus = useCallback(
....@@ -58,7 +236,7 @@
58236
59237 // Handle incoming WebSocket messages
60238 useEffect(() => {
61
- onMessageReceived.current = (data: WsIncoming) => {
239
+ onMessageReceived.current = async (data: WsIncoming) => {
62240 switch (data.type) {
63241 case "text": {
64242 const msg: Message = {
....@@ -69,31 +247,37 @@
69247 timestamp: Date.now(),
70248 status: "sent",
71249 };
72
- setMessages((prev) => [...prev, msg]);
250
+ addMessageToActive(msg);
251
+ notifyIncomingMessage("PAILot", data.content ?? "New message");
73252 break;
74253 }
75254 case "voice": {
255
+ let audioUri: string | undefined;
256
+ if (data.audioBase64) {
257
+ try {
258
+ audioUri = await saveBase64Audio(data.audioBase64);
259
+ } catch {
260
+ // fallback: no playable audio
261
+ }
262
+ }
76263 const msg: Message = {
77264 id: generateId(),
78265 role: "assistant",
79266 type: "voice",
80267 content: data.content ?? "",
81
- audioUri: data.audioBase64
82
- ? `data:audio/mp4;base64,${data.audioBase64}`
83
- : undefined,
268
+ audioUri,
84269 timestamp: Date.now(),
85270 status: "sent",
86271 };
87
- setMessages((prev) => [...prev, msg]);
272
+ addMessageToActive(msg);
273
+ notifyIncomingMessage("PAILot", data.content ?? "Voice message");
88274 if (msg.audioUri) {
89275 playAudio(msg.audioUri).catch(() => {});
90276 }
91277 break;
92278 }
93279 case "image": {
94
- // Store as latest screenshot for navigation mode
95280 setLatestScreenshot(data.imageBase64);
96
- // Also add to chat as an image message
97281 const msg: Message = {
98282 id: generateId(),
99283 role: "assistant",
....@@ -103,11 +287,15 @@
103287 timestamp: Date.now(),
104288 status: "sent",
105289 };
106
- setMessages((prev) => [...prev, msg]);
290
+ addMessageToActive(msg);
291
+ notifyIncomingMessage("PAILot", data.caption ?? "New image");
107292 break;
108293 }
109294 case "sessions": {
110
- setSessions(data.sessions);
295
+ const incoming = data.sessions as WsSession[];
296
+ setSessions(incoming);
297
+ syncActiveFromSessions(incoming);
298
+ needsSync.current = false;
111299 break;
112300 }
113301 case "session_switched": {
....@@ -118,7 +306,8 @@
118306 content: `Switched to ${data.name}`,
119307 timestamp: Date.now(),
120308 };
121
- setMessages((prev) => [...prev, msg]);
309
+ addMessageToActive(msg);
310
+ sendCommand("sessions");
122311 break;
123312 }
124313 case "session_renamed": {
....@@ -129,8 +318,7 @@
129318 content: `Renamed to ${data.name}`,
130319 timestamp: Date.now(),
131320 };
132
- setMessages((prev) => [...prev, msg]);
133
- // Refresh sessions to show updated name
321
+ addMessageToActive(msg);
134322 sendCommand("sessions");
135323 break;
136324 }
....@@ -142,7 +330,7 @@
142330 content: data.message,
143331 timestamp: Date.now(),
144332 };
145
- setMessages((prev) => [...prev, msg]);
333
+ addMessageToActive(msg);
146334 break;
147335 }
148336 }
....@@ -151,7 +339,7 @@
151339 return () => {
152340 onMessageReceived.current = null;
153341 };
154
- }, [onMessageReceived, sendCommand]);
342
+ }, [onMessageReceived, sendCommand, addMessageToActive, syncActiveFromSessions]);
155343
156344 const sendTextMessage = useCallback(
157345 (text: string) => {
....@@ -164,11 +352,11 @@
164352 timestamp: Date.now(),
165353 status: "sending",
166354 };
167
- addMessage(msg);
355
+ addMessageToActive(msg);
168356 const sent = wsSend(text);
169357 updateMessageStatus(id, sent ? "sent" : "error");
170358 },
171
- [wsSend, addMessage, updateMessageStatus]
359
+ [wsSend, addMessageToActive, updateMessageStatus]
172360 );
173361
174362 const sendVoiceMessage = useCallback(
....@@ -184,7 +372,7 @@
184372 status: "sending",
185373 duration: durationMs,
186374 };
187
- addMessage(msg);
375
+ addMessageToActive(msg);
188376 try {
189377 const base64 = await encodeAudioToBase64(audioUri);
190378 const sent = wsVoice(base64);
....@@ -194,11 +382,37 @@
194382 updateMessageStatus(id, "error");
195383 }
196384 },
197
- [wsVoice, addMessage, updateMessageStatus]
385
+ [wsVoice, addMessageToActive, updateMessageStatus]
386
+ );
387
+
388
+ const sendImageMessage = useCallback(
389
+ (imageBase64: string, caption: string, mimeType: string) => {
390
+ const id = generateId();
391
+ const msg: Message = {
392
+ id,
393
+ role: "user",
394
+ type: "image",
395
+ content: caption || "Photo",
396
+ imageBase64,
397
+ timestamp: Date.now(),
398
+ status: "sending",
399
+ };
400
+ addMessageToActive(msg);
401
+ const sent = wsImageSend(imageBase64, caption, mimeType);
402
+ updateMessageStatus(id, sent ? "sent" : "error");
403
+ },
404
+ [wsImageSend, addMessageToActive, updateMessageStatus]
198405 );
199406
200407 const clearMessages = useCallback(() => {
201408 setMessages([]);
409
+ setActiveSessionId((id) => {
410
+ if (id) {
411
+ messagesMapRef.current[id] = [];
412
+ clearPersistedMessages(id);
413
+ }
414
+ return id;
415
+ });
202416 }, []);
203417
204418 // --- Session management ---
....@@ -208,9 +422,16 @@
208422
209423 const switchSession = useCallback(
210424 (sessionId: string) => {
425
+ setActiveSessionId((prev) => {
426
+ if (prev) {
427
+ messagesMapRef.current[prev] = messages;
428
+ debouncedSave(messagesMapRef.current);
429
+ }
430
+ return prev;
431
+ });
211432 sendCommand("switch", { sessionId });
212433 },
213
- [sendCommand]
434
+ [sendCommand, messages]
214435 );
215436
216437 const renameSession = useCallback(
....@@ -219,6 +440,25 @@
219440 },
220441 [sendCommand]
221442 );
443
+
444
+ const removeSession = useCallback(
445
+ (sessionId: string) => {
446
+ sendCommand("remove", { sessionId });
447
+ delete messagesMapRef.current[sessionId];
448
+ deletePersistedSession(sessionId);
449
+ setUnreadCounts((u) => {
450
+ if (!u[sessionId]) return u;
451
+ const next = { ...u };
452
+ delete next[sessionId];
453
+ return next;
454
+ });
455
+ },
456
+ [sendCommand]
457
+ );
458
+
459
+ const createSession = useCallback(() => {
460
+ sendCommand("create");
461
+ }, [sendCommand]);
222462
223463 // --- Screenshot / navigation ---
224464 const requestScreenshot = useCallback(() => {
....@@ -238,11 +478,16 @@
238478 messages,
239479 sendTextMessage,
240480 sendVoiceMessage,
481
+ sendImageMessage,
241482 clearMessages,
242483 sessions,
484
+ activeSessionId,
243485 requestSessions,
244486 switchSession,
245487 renameSession,
488
+ removeSession,
489
+ createSession,
490
+ unreadCounts,
246491 latestScreenshot,
247492 requestScreenshot,
248493 sendNavKey,
contexts/ConnectionContext.tsx
....@@ -14,6 +14,7 @@
1414 WsOutgoing,
1515 } from "../types";
1616 import { wsClient } from "../services/websocket";
17
+import { sendWol, isValidMac } from "../services/wol";
1718
1819 const SECURE_STORE_KEY = "pailot_server_config";
1920
....@@ -24,6 +25,7 @@
2425 disconnect: () => void;
2526 sendTextMessage: (text: string) => boolean;
2627 sendVoiceMessage: (audioBase64: string, transcript?: string) => boolean;
28
+ sendImageMessage: (imageBase64: string, caption: string, mimeType: string) => boolean;
2729 sendCommand: (command: string, args?: Record<string, unknown>) => boolean;
2830 saveServerConfig: (config: ServerConfig) => Promise<void>;
2931 onMessageReceived: React.MutableRefObject<
....@@ -52,7 +54,14 @@
5254 onClose: () => setStatus("disconnected"),
5355 onError: () => setStatus("disconnected"),
5456 onMessage: (data) => {
55
- onMessageReceived.current?.(data as WsIncoming);
57
+ const msg = data as unknown as WsIncoming;
58
+ // Handle server-side status changes (compaction indicator)
59
+ if (msg.type === "status") {
60
+ if (msg.status === "compacting") setStatus("compacting");
61
+ else if (msg.status === "online") setStatus("connected");
62
+ return;
63
+ }
64
+ onMessageReceived.current?.(msg);
5665 },
5766 });
5867 }, []);
....@@ -70,10 +79,21 @@
7079 }
7180 }
7281
73
- function connectToServer(config: ServerConfig) {
82
+ async function connectToServer(config: ServerConfig) {
7483 setStatus("connecting");
75
- const url = `ws://${config.host}:${config.port}`;
76
- wsClient.connect(url);
84
+
85
+ // Fire-and-forget WoL — never block the WebSocket connection
86
+ if (config.macAddress && isValidMac(config.macAddress)) {
87
+ sendWol(config.macAddress, config.host).catch(() => {});
88
+ }
89
+
90
+ // Build URL list: local first (preferred), then remote
91
+ const urls: string[] = [];
92
+ if (config.localHost) {
93
+ urls.push(`ws://${config.localHost}:${config.port}`);
94
+ }
95
+ urls.push(`ws://${config.host}:${config.port}`);
96
+ wsClient.connect(urls);
7797 }
7898
7999 const connect = useCallback(
....@@ -110,6 +130,13 @@
110130 []
111131 );
112132
133
+ const sendImageMessage = useCallback(
134
+ (imageBase64: string, caption: string = "", mimeType: string = "image/jpeg"): boolean => {
135
+ return wsClient.send({ type: "image", imageBase64, caption, mimeType });
136
+ },
137
+ []
138
+ );
139
+
113140 const sendCommand = useCallback(
114141 (command: string, args?: Record<string, unknown>): boolean => {
115142 const msg: WsOutgoing = { type: "command", command, args };
....@@ -127,6 +154,7 @@
127154 disconnect,
128155 sendTextMessage,
129156 sendVoiceMessage,
157
+ sendImageMessage,
130158 sendCommand,
131159 saveServerConfig,
132160 onMessageReceived,
contexts/ThemeContext.tsx
....@@ -0,0 +1,97 @@
1
+import React, { createContext, useCallback, useContext, useEffect, useState } from "react";
2
+import { useColorScheme } from "react-native";
3
+import * as SecureStore from "expo-secure-store";
4
+
5
+const THEME_KEY = "pailot_theme";
6
+
7
+type ThemeMode = "light" | "dark" | "system";
8
+
9
+export interface ThemeColors {
10
+ bg: string;
11
+ bgSecondary: string;
12
+ bgTertiary: string;
13
+ text: string;
14
+ textSecondary: string;
15
+ textMuted: string;
16
+ border: string;
17
+ accent: string;
18
+ accentBg: string;
19
+ danger: string;
20
+}
21
+
22
+const darkColors: ThemeColors = {
23
+ bg: "#0A0A0F",
24
+ bgSecondary: "#14141F",
25
+ bgTertiary: "#1E1E2E",
26
+ text: "#E8E8F0",
27
+ textSecondary: "#9898B0",
28
+ textMuted: "#5A5A78",
29
+ border: "#2E2E45",
30
+ accent: "#4A9EFF",
31
+ accentBg: "#4A9EFF18",
32
+ danger: "#FF3B30",
33
+};
34
+
35
+const lightColors: ThemeColors = {
36
+ bg: "#FFFFFF",
37
+ bgSecondary: "#F5F5F7",
38
+ bgTertiary: "#EEEEF0",
39
+ text: "#1C1C1E",
40
+ textSecondary: "#636366",
41
+ textMuted: "#AEAEB2",
42
+ border: "#D1D1D6",
43
+ accent: "#007AFF",
44
+ accentBg: "#007AFF14",
45
+ danger: "#FF3B30",
46
+};
47
+
48
+interface ThemeContextValue {
49
+ mode: ThemeMode;
50
+ isDark: boolean;
51
+ colors: ThemeColors;
52
+ setMode: (mode: ThemeMode) => void;
53
+ cycleMode: () => void;
54
+}
55
+
56
+const ThemeContext = createContext<ThemeContextValue | null>(null);
57
+
58
+export function ThemeProvider({ children }: { children: React.ReactNode }) {
59
+ const systemScheme = useColorScheme();
60
+ const [mode, setModeState] = useState<ThemeMode>("dark");
61
+
62
+ useEffect(() => {
63
+ SecureStore.getItemAsync(THEME_KEY).then((stored) => {
64
+ if (stored === "light" || stored === "dark" || stored === "system") {
65
+ setModeState(stored);
66
+ }
67
+ });
68
+ }, []);
69
+
70
+ const setMode = useCallback((m: ThemeMode) => {
71
+ setModeState(m);
72
+ SecureStore.setItemAsync(THEME_KEY, m);
73
+ }, []);
74
+
75
+ const cycleMode = useCallback(() => {
76
+ setModeState((prev) => {
77
+ const next = prev === "dark" ? "light" : prev === "light" ? "system" : "dark";
78
+ SecureStore.setItemAsync(THEME_KEY, next);
79
+ return next;
80
+ });
81
+ }, []);
82
+
83
+ const isDark = mode === "dark" || (mode === "system" && systemScheme !== "light");
84
+ const colors = isDark ? darkColors : lightColors;
85
+
86
+ return (
87
+ <ThemeContext.Provider value={{ mode, isDark, colors, setMode, cycleMode }}>
88
+ {children}
89
+ </ThemeContext.Provider>
90
+ );
91
+}
92
+
93
+export function useTheme() {
94
+ const ctx = useContext(ThemeContext);
95
+ if (!ctx) throw new Error("useTheme must be used within ThemeProvider");
96
+ return ctx;
97
+}
memory/MEMORY.md
....@@ -0,0 +1,4 @@
1
+# Memory
2
+
3
+Project-specific memory for PAI sessions.
4
+Add persistent notes, reminders, and context here.
package-lock.json
....@@ -15,10 +15,11 @@
1515 "expo-constants": "~55.0.7",
1616 "expo-file-system": "~55.0.10",
1717 "expo-haptics": "~55.0.8",
18
+ "expo-image-picker": "~55.0.11",
1819 "expo-linking": "~55.0.7",
1920 "expo-router": "~55.0.3",
2021 "expo-secure-store": "~55.0.8",
21
- "expo-speech-recognition": "^3.1.1",
22
+ "expo-sharing": "~55.0.11",
2223 "expo-splash-screen": "~55.0.10",
2324 "expo-status-bar": "~55.0.4",
2425 "expo-system-ui": "~55.0.9",
....@@ -27,11 +28,13 @@
2728 "react": "19.2.0",
2829 "react-dom": "^19.2.4",
2930 "react-native": "0.83.2",
31
+ "react-native-draggable-flatlist": "^4.0.3",
3032 "react-native-gesture-handler": "~2.30.0",
3133 "react-native-reanimated": "4.2.1",
3234 "react-native-safe-area-context": "~5.6.2",
3335 "react-native-screens": "~4.23.0",
3436 "react-native-svg": "15.15.3",
37
+ "react-native-udp": "^4.1.7",
3538 "react-native-web": "^0.21.0",
3639 "react-native-worklets": "0.7.2"
3740 },
....@@ -3764,6 +3767,30 @@
37643767 "node-int64": "^0.4.0"
37653768 }
37663769 },
3770
+ "node_modules/buffer": {
3771
+ "version": "5.7.1",
3772
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
3773
+ "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
3774
+ "funding": [
3775
+ {
3776
+ "type": "github",
3777
+ "url": "https://github.com/sponsors/feross"
3778
+ },
3779
+ {
3780
+ "type": "patreon",
3781
+ "url": "https://www.patreon.com/feross"
3782
+ },
3783
+ {
3784
+ "type": "consulting",
3785
+ "url": "https://feross.org/support"
3786
+ }
3787
+ ],
3788
+ "license": "MIT",
3789
+ "dependencies": {
3790
+ "base64-js": "^1.3.1",
3791
+ "ieee754": "^1.1.13"
3792
+ }
3793
+ },
37673794 "node_modules/buffer-from": {
37683795 "version": "1.1.2",
37693796 "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
....@@ -4518,6 +4545,15 @@
45184545 "node": ">=6"
45194546 }
45204547 },
4548
+ "node_modules/events": {
4549
+ "version": "3.3.0",
4550
+ "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
4551
+ "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
4552
+ "license": "MIT",
4553
+ "engines": {
4554
+ "node": ">=0.8.x"
4555
+ }
4556
+ },
45214557 "node_modules/expo": {
45224558 "version": "55.0.4",
45234559 "resolved": "https://registry.npmjs.org/expo/-/expo-55.0.4.tgz",
....@@ -4660,6 +4696,27 @@
46604696 "react-native-web": {
46614697 "optional": true
46624698 }
4699
+ }
4700
+ },
4701
+ "node_modules/expo-image-loader": {
4702
+ "version": "55.0.0",
4703
+ "resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-55.0.0.tgz",
4704
+ "integrity": "sha512-NOjp56wDrfuA5aiNAybBIjqIn1IxKeGJ8CECWZncQ/GzjZfyTYAHTCyeApYkdKkMBLHINzI4BbTGSlbCa0fXXQ==",
4705
+ "license": "MIT",
4706
+ "peerDependencies": {
4707
+ "expo": "*"
4708
+ }
4709
+ },
4710
+ "node_modules/expo-image-picker": {
4711
+ "version": "55.0.11",
4712
+ "resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-55.0.11.tgz",
4713
+ "integrity": "sha512-geJklIGdAR2N16iSk86oyJe7QgX5RpqDX1FjKpxO53fF4D0eBmg5Irm6gRwT0b+DHP1kJevZgzzbVJsRAV362g==",
4714
+ "license": "MIT",
4715
+ "dependencies": {
4716
+ "expo-image-loader": "~55.0.0"
4717
+ },
4718
+ "peerDependencies": {
4719
+ "expo": "*"
46634720 }
46644721 },
46654722 "node_modules/expo-linking": {
....@@ -4863,11 +4920,16 @@
48634920 "node": ">=20.16.0"
48644921 }
48654922 },
4866
- "node_modules/expo-speech-recognition": {
4867
- "version": "3.1.1",
4868
- "resolved": "https://registry.npmjs.org/expo-speech-recognition/-/expo-speech-recognition-3.1.1.tgz",
4869
- "integrity": "sha512-+1rviv+ZecAokY8PUfr3XJuhS4t0uKccewIPPUk5ooeEt5xKEWr6XYpKm3ggapPdJQbgMTjWbmSPT1ahTMyIqA==",
4923
+ "node_modules/expo-sharing": {
4924
+ "version": "55.0.11",
4925
+ "resolved": "https://registry.npmjs.org/expo-sharing/-/expo-sharing-55.0.11.tgz",
4926
+ "integrity": "sha512-YlVez832W0sYR2KJY4Dr8ON9aC+Wp8a/r40eQyhoHT9Tetkr2KBM7tWLT0CGKRuTTnrqJL1C51UacLkHJ9zmNA==",
48704927 "license": "MIT",
4928
+ "dependencies": {
4929
+ "@expo/config-plugins": "^55.0.6",
4930
+ "@expo/config-types": "^55.0.5",
4931
+ "@expo/plist": "^0.5.2"
4932
+ },
48714933 "peerDependencies": {
48724934 "expo": "*",
48734935 "react": "*",
....@@ -5623,6 +5685,26 @@
56235685 "version": "1.1.0",
56245686 "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz",
56255687 "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
5688
+ "license": "BSD-3-Clause"
5689
+ },
5690
+ "node_modules/ieee754": {
5691
+ "version": "1.2.1",
5692
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
5693
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
5694
+ "funding": [
5695
+ {
5696
+ "type": "github",
5697
+ "url": "https://github.com/sponsors/feross"
5698
+ },
5699
+ {
5700
+ "type": "patreon",
5701
+ "url": "https://www.patreon.com/feross"
5702
+ },
5703
+ {
5704
+ "type": "consulting",
5705
+ "url": "https://feross.org/support"
5706
+ }
5707
+ ],
56265708 "license": "BSD-3-Clause"
56275709 },
56285710 "node_modules/ignore": {
....@@ -8180,6 +8262,20 @@
81808262 "url": "https://opencollective.com/parcel"
81818263 }
81828264 },
8265
+ "node_modules/react-native-draggable-flatlist": {
8266
+ "version": "4.0.3",
8267
+ "resolved": "https://registry.npmjs.org/react-native-draggable-flatlist/-/react-native-draggable-flatlist-4.0.3.tgz",
8268
+ "integrity": "sha512-2F4x5BFieWdGq9SetD2nSAR7s7oQCSgNllYgERRXXtNfSOuAGAVbDb/3H3lP0y5f7rEyNwabKorZAD/SyyNbDw==",
8269
+ "license": "MIT",
8270
+ "dependencies": {
8271
+ "@babel/preset-typescript": "^7.17.12"
8272
+ },
8273
+ "peerDependencies": {
8274
+ "react-native": ">=0.64.0",
8275
+ "react-native-gesture-handler": ">=2.0.0",
8276
+ "react-native-reanimated": ">=2.8.0"
8277
+ }
8278
+ },
81838279 "node_modules/react-native-gesture-handler": {
81848280 "version": "2.30.0",
81858281 "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.30.0.tgz",
....@@ -8271,6 +8367,16 @@
82718367 "react-native": "*"
82728368 }
82738369 },
8370
+ "node_modules/react-native-udp": {
8371
+ "version": "4.1.7",
8372
+ "resolved": "https://registry.npmjs.org/react-native-udp/-/react-native-udp-4.1.7.tgz",
8373
+ "integrity": "sha512-NUE3zewu61NCdSsLlj+l0ad6qojcVEZPT4hVG/x6DU9U4iCzwtfZSASh9vm7teAcVzLkdD+cO3411LHshAi/wA==",
8374
+ "license": "MIT",
8375
+ "dependencies": {
8376
+ "buffer": "^5.6.0",
8377
+ "events": "^3.1.0"
8378
+ }
8379
+ },
82748380 "node_modules/react-native-web": {
82758381 "version": "0.21.2",
82768382 "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
package.json
....@@ -16,10 +16,11 @@
1616 "expo-constants": "~55.0.7",
1717 "expo-file-system": "~55.0.10",
1818 "expo-haptics": "~55.0.8",
19
+ "expo-image-picker": "~55.0.11",
1920 "expo-linking": "~55.0.7",
2021 "expo-router": "~55.0.3",
2122 "expo-secure-store": "~55.0.8",
22
- "expo-speech-recognition": "^3.1.1",
23
+ "expo-sharing": "~55.0.11",
2324 "expo-splash-screen": "~55.0.10",
2425 "expo-status-bar": "~55.0.4",
2526 "expo-system-ui": "~55.0.9",
....@@ -28,11 +29,13 @@
2829 "react": "19.2.0",
2930 "react-dom": "^19.2.4",
3031 "react-native": "0.83.2",
32
+ "react-native-draggable-flatlist": "^4.0.3",
3133 "react-native-gesture-handler": "~2.30.0",
3234 "react-native-reanimated": "4.2.1",
3335 "react-native-safe-area-context": "~5.6.2",
3436 "react-native-screens": "~4.23.0",
3537 "react-native-svg": "15.15.3",
38
+ "react-native-udp": "^4.1.7",
3639 "react-native-web": "^0.21.0",
3740 "react-native-worklets": "0.7.2"
3841 },
services/audio.ts
....@@ -3,6 +3,7 @@
33 requestRecordingPermissionsAsync,
44 setAudioModeAsync,
55 } from "expo-audio";
6
+import * as LegacyFileSystem from "expo-file-system/legacy";
67
78 export interface RecordingResult {
89 uri: string;
....@@ -10,41 +11,103 @@
1011 }
1112
1213 let currentPlayer: ReturnType<typeof createAudioPlayer> | null = null;
14
+const playingListeners = new Set<(playing: boolean) => void>();
15
+
16
+// Audio queue for chaining sequential voice notes
17
+const audioQueue: Array<{ uri: string; onFinish?: () => void }> = [];
18
+let processingQueue = false;
19
+
20
+function notifyListeners(playing: boolean): void {
21
+ for (const cb of playingListeners) cb(playing);
22
+}
23
+
24
+export function onPlayingChange(cb: (playing: boolean) => void): () => void {
25
+ playingListeners.add(cb);
26
+ return () => { playingListeners.delete(cb); };
27
+}
1328
1429 export async function requestPermissions(): Promise<boolean> {
1530 const { status } = await requestRecordingPermissionsAsync();
1631 return status === "granted";
1732 }
1833
34
+let audioCounter = 0;
35
+
36
+/**
37
+ * Convert a base64 audio string to a file URI.
38
+ */
39
+export async function saveBase64Audio(base64: string, ext = "m4a"): Promise<string> {
40
+ const tmpPath = `${LegacyFileSystem.cacheDirectory}pailot-voice-${++audioCounter}.${ext}`;
41
+ await LegacyFileSystem.writeAsStringAsync(tmpPath, base64, {
42
+ encoding: LegacyFileSystem.EncodingType.Base64,
43
+ });
44
+ return tmpPath;
45
+}
46
+
47
+/**
48
+ * Queue audio for playback. Multiple calls chain sequentially —
49
+ * the next voice note plays only after the current one finishes.
50
+ */
1951 export async function playAudio(
2052 uri: string,
2153 onFinish?: () => void
2254 ): Promise<void> {
23
- try {
24
- await stopPlayback();
25
-
26
- await setAudioModeAsync({
27
- playsInSilentMode: true,
28
- });
29
-
30
- const player = createAudioPlayer(uri);
31
- currentPlayer = player;
32
-
33
- player.addListener("playbackStatusUpdate", (status) => {
34
- if (!status.playing && status.currentTime >= status.duration && status.duration > 0) {
35
- onFinish?.();
36
- player.remove();
37
- if (currentPlayer === player) currentPlayer = null;
38
- }
39
- });
40
-
41
- player.play();
42
- } catch (error) {
43
- console.error("Failed to play audio:", error);
55
+ audioQueue.push({ uri, onFinish });
56
+ if (!processingQueue) {
57
+ processAudioQueue();
4458 }
4559 }
4660
61
+async function processAudioQueue(): Promise<void> {
62
+ if (processingQueue) return;
63
+ processingQueue = true;
64
+
65
+ while (audioQueue.length > 0) {
66
+ const item = audioQueue.shift()!;
67
+ await playOneAudio(item.uri, item.onFinish);
68
+ }
69
+
70
+ processingQueue = false;
71
+}
72
+
73
+function playOneAudio(uri: string, onFinish?: () => void): Promise<void> {
74
+ return new Promise<void>(async (resolve) => {
75
+ try {
76
+ await setAudioModeAsync({ playsInSilentMode: true });
77
+
78
+ const player = createAudioPlayer(uri);
79
+ currentPlayer = player;
80
+ notifyListeners(true);
81
+
82
+ player.addListener("playbackStatusUpdate", (status) => {
83
+ if (!status.playing && status.currentTime >= status.duration && status.duration > 0) {
84
+ onFinish?.();
85
+ player.remove();
86
+ if (currentPlayer === player) {
87
+ currentPlayer = null;
88
+ if (audioQueue.length === 0) notifyListeners(false);
89
+ }
90
+ resolve();
91
+ }
92
+ });
93
+
94
+ player.play();
95
+ } catch (error) {
96
+ console.error("Failed to play audio:", error);
97
+ resolve();
98
+ }
99
+ });
100
+}
101
+
102
+export function isPlaying(): boolean {
103
+ return currentPlayer !== null;
104
+}
105
+
106
+/**
107
+ * Stop current playback and clear the queue.
108
+ */
47109 export async function stopPlayback(): Promise<void> {
110
+ audioQueue.length = 0;
48111 if (currentPlayer) {
49112 try {
50113 currentPlayer.pause();
....@@ -53,13 +116,13 @@
53116 // Ignore cleanup errors
54117 }
55118 currentPlayer = null;
119
+ notifyListeners(false);
56120 }
57121 }
58122
59123 export async function encodeAudioToBase64(uri: string): Promise<string> {
60
- const FileSystem = await import("expo-file-system");
61
- const result = await FileSystem.readAsStringAsync(uri, {
62
- encoding: FileSystem.EncodingType.Base64,
124
+ const result = await LegacyFileSystem.readAsStringAsync(uri, {
125
+ encoding: LegacyFileSystem.EncodingType.Base64,
63126 });
64127 return result;
65128 }
services/notifications.ts
....@@ -0,0 +1,40 @@
1
+/**
2
+ * Local push notifications for background message delivery.
3
+ *
4
+ * Uses a minimal Objective-C native module (ios/LocalNotifications/) that wraps
5
+ * UNUserNotificationCenter directly. No aps-environment entitlement needed.
6
+ */
7
+import { AppState, NativeModules } from "react-native";
8
+
9
+const { LocalNotifications } = NativeModules;
10
+
11
+let permissionGranted = false;
12
+
13
+/** Request notification permissions. Call once at app startup. */
14
+export async function requestNotificationPermissions(): Promise<boolean> {
15
+ try {
16
+ if (!LocalNotifications) return false;
17
+ permissionGranted = await LocalNotifications.requestPermissions();
18
+ } catch {
19
+ permissionGranted = false;
20
+ }
21
+ return permissionGranted;
22
+}
23
+
24
+/** Returns true if the app is currently in the background or inactive. */
25
+export function isAppBackgrounded(): boolean {
26
+ return AppState.currentState !== "active";
27
+}
28
+
29
+/** Fire a local notification for an incoming message. */
30
+export async function notifyIncomingMessage(
31
+ title: string,
32
+ body: string,
33
+): Promise<void> {
34
+ if (!permissionGranted || !isAppBackgrounded() || !LocalNotifications) return;
35
+ try {
36
+ await LocalNotifications.notify(title, body);
37
+ } catch {
38
+ // Best-effort
39
+ }
40
+}
services/websocket.ts
....@@ -1,5 +1,6 @@
1
-import { WebSocketMessage } from "../types";
1
+import { WsOutgoing } from "../types";
22
3
+type WebSocketMessage = Record<string, unknown>;
34 type MessageCallback = (data: WebSocketMessage) => void;
45 type StatusCallback = () => void;
56 type ErrorCallback = (error: Event) => void;
....@@ -14,97 +15,125 @@
1415 const INITIAL_RECONNECT_DELAY = 1000;
1516 const MAX_RECONNECT_DELAY = 30000;
1617 const RECONNECT_MULTIPLIER = 2;
18
+const LOCAL_TIMEOUT = 2500;
1719
1820 export class WebSocketClient {
1921 private ws: WebSocket | null = null;
20
- private url: string = "";
22
+ private urls: string[] = [];
23
+ private urlIndex: number = 0;
2124 private reconnectDelay: number = INITIAL_RECONNECT_DELAY;
2225 private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
26
+ private localTimer: ReturnType<typeof setTimeout> | null = null;
2327 private shouldReconnect: boolean = false;
28
+ private connected: boolean = false;
2429 private callbacks: WebSocketClientOptions = {};
2530
2631 setCallbacks(callbacks: WebSocketClientOptions) {
2732 this.callbacks = callbacks;
2833 }
2934
30
- connect(url: string) {
31
- this.url = url;
35
+ connect(urls: string[]) {
36
+ this.urls = urls.filter(Boolean);
37
+ if (this.urls.length === 0) return;
3238 this.shouldReconnect = true;
3339 this.reconnectDelay = INITIAL_RECONNECT_DELAY;
34
- this.openConnection();
40
+ this.urlIndex = 0;
41
+ this.connected = false;
42
+ this.tryUrl();
3543 }
3644
37
- private openConnection() {
45
+ private cleanup() {
46
+ if (this.localTimer) { clearTimeout(this.localTimer); this.localTimer = null; }
47
+ if (this.reconnectTimer) { clearTimeout(this.reconnectTimer); this.reconnectTimer = null; }
3848 if (this.ws) {
39
- this.ws.close();
49
+ const old = this.ws;
4050 this.ws = null;
51
+ old.onopen = null;
52
+ old.onclose = null;
53
+ old.onerror = null;
54
+ old.onmessage = null;
55
+ try { old.close(); } catch { /* ignore */ }
56
+ }
57
+ }
58
+
59
+ private tryUrl() {
60
+ this.cleanup();
61
+
62
+ const url = this.urls[this.urlIndex];
63
+ if (!url) return;
64
+
65
+ const ws = new WebSocket(url);
66
+ this.ws = ws;
67
+
68
+ // If trying local (index 0) and we have a remote fallback,
69
+ // give local 2.5s before switching to remote
70
+ if (this.urlIndex === 0 && this.urls.length > 1) {
71
+ this.localTimer = setTimeout(() => {
72
+ this.localTimer = null;
73
+ if (this.connected) return; // already connected, ignore
74
+ // Local didn't connect in time — try remote
75
+ this.urlIndex = 1;
76
+ this.tryUrl();
77
+ }, LOCAL_TIMEOUT);
4178 }
4279
43
- try {
44
- this.ws = new WebSocket(this.url);
80
+ ws.onopen = () => {
81
+ if (ws !== this.ws) return; // stale
82
+ this.connected = true;
83
+ if (this.localTimer) { clearTimeout(this.localTimer); this.localTimer = null; }
84
+ this.reconnectDelay = INITIAL_RECONNECT_DELAY;
85
+ this.callbacks.onOpen?.();
86
+ };
4587
46
- this.ws.onopen = () => {
47
- this.reconnectDelay = INITIAL_RECONNECT_DELAY;
48
- this.callbacks.onOpen?.();
49
- };
88
+ ws.onmessage = (event) => {
89
+ if (ws !== this.ws) return; // stale
90
+ try {
91
+ const data = JSON.parse(event.data) as WebSocketMessage;
92
+ this.callbacks.onMessage?.(data);
93
+ } catch {
94
+ this.callbacks.onMessage?.({ type: "text", content: String(event.data) });
95
+ }
96
+ };
5097
51
- this.ws.onmessage = (event) => {
52
- try {
53
- const data = JSON.parse(event.data) as WebSocketMessage;
54
- this.callbacks.onMessage?.(data);
55
- } catch {
56
- // Non-JSON message — treat as plain text
57
- const data: WebSocketMessage = {
58
- type: "text",
59
- content: String(event.data),
60
- };
61
- this.callbacks.onMessage?.(data);
62
- }
63
- };
64
-
65
- this.ws.onclose = () => {
66
- this.callbacks.onClose?.();
67
- if (this.shouldReconnect) {
68
- this.scheduleReconnect();
69
- }
70
- };
71
-
72
- this.ws.onerror = (error) => {
73
- this.callbacks.onError?.(error);
74
- };
75
- } catch {
98
+ ws.onclose = () => {
99
+ if (ws !== this.ws) return; // stale
100
+ this.connected = false;
101
+ this.callbacks.onClose?.();
76102 if (this.shouldReconnect) {
77103 this.scheduleReconnect();
78104 }
79
- }
105
+ };
106
+
107
+ ws.onerror = () => {
108
+ if (ws !== this.ws) return; // stale
109
+ // Don't do anything here — onclose always fires after onerror
110
+ // and handles reconnect. Just swallow the error event.
111
+ };
80112 }
81113
82114 private scheduleReconnect() {
83
- if (this.reconnectTimer) {
84
- clearTimeout(this.reconnectTimer);
85
- }
115
+ if (this.reconnectTimer) clearTimeout(this.reconnectTimer);
86116 this.reconnectTimer = setTimeout(() => {
117
+ this.reconnectTimer = null;
87118 this.reconnectDelay = Math.min(
88119 this.reconnectDelay * RECONNECT_MULTIPLIER,
89120 MAX_RECONNECT_DELAY
90121 );
91
- this.openConnection();
122
+ // Alternate between URLs on each reconnect attempt
123
+ if (this.urls.length > 1) {
124
+ this.urlIndex = this.urlIndex === 0 ? 1 : 0;
125
+ }
126
+ this.tryUrl();
92127 }, this.reconnectDelay);
93128 }
94129
95130 disconnect() {
96131 this.shouldReconnect = false;
97
- if (this.reconnectTimer) {
98
- clearTimeout(this.reconnectTimer);
99
- this.reconnectTimer = null;
100
- }
101
- if (this.ws) {
102
- this.ws.close();
103
- this.ws = null;
104
- }
132
+ this.connected = false;
133
+ this.cleanup();
105134 }
106135
107
- send(message: WebSocketMessage) {
136
+ send(message: WsOutgoing) {
108137 if (this.ws && this.ws.readyState === WebSocket.OPEN) {
109138 this.ws.send(JSON.stringify(message));
110139 return true;
....@@ -117,7 +146,11 @@
117146 }
118147
119148 get isConnected(): boolean {
120
- return this.ws?.readyState === WebSocket.OPEN;
149
+ return this.connected;
150
+ }
151
+
152
+ get currentUrl(): string {
153
+ return this.urls[this.urlIndex] ?? "";
121154 }
122155 }
123156
services/wol.ts
....@@ -0,0 +1,101 @@
1
+/**
2
+ * Wake-on-LAN service — sends a magic packet to wake a sleeping Mac.
3
+ *
4
+ * The magic packet is 6 bytes of 0xFF followed by the target MAC address
5
+ * repeated 16 times, sent as a UDP broadcast on port 9.
6
+ */
7
+import dgram from "react-native-udp";
8
+
9
+function parseMac(mac: string): number[] {
10
+ const parts = mac
11
+ .replace(/[:-]/g, "")
12
+ .match(/.{2}/g);
13
+ if (!parts || parts.length !== 6) {
14
+ throw new Error(`Invalid MAC address: ${mac}`);
15
+ }
16
+ return parts.map((h) => parseInt(h, 16));
17
+}
18
+
19
+function buildMagicPacket(mac: string): Uint8Array {
20
+ const macBytes = parseMac(mac);
21
+ const packet = new Uint8Array(102);
22
+
23
+ // 6 bytes of 0xFF
24
+ for (let i = 0; i < 6; i++) {
25
+ packet[i] = 0xff;
26
+ }
27
+
28
+ // MAC address repeated 16 times
29
+ for (let i = 0; i < 16; i++) {
30
+ const offset = 6 + i * 6;
31
+ for (let j = 0; j < 6; j++) {
32
+ packet[offset + j] = macBytes[j];
33
+ }
34
+ }
35
+
36
+ return packet;
37
+}
38
+
39
+/**
40
+ * Send a Wake-on-LAN magic packet to the given MAC address.
41
+ * Sends to both the subnet broadcast (derived from host) and 255.255.255.255.
42
+ */
43
+export async function sendWol(mac: string, host?: string): Promise<void> {
44
+ const packet = buildMagicPacket(mac);
45
+
46
+ // Derive broadcast address from host IP (replace last octet with 255)
47
+ const broadcastAddresses = ["255.255.255.255"];
48
+ if (host) {
49
+ const parts = host.split(".");
50
+ if (parts.length === 4) {
51
+ parts[3] = "255";
52
+ const subnetBroadcast = parts.join(".");
53
+ if (!broadcastAddresses.includes(subnetBroadcast)) {
54
+ broadcastAddresses.push(subnetBroadcast);
55
+ }
56
+ }
57
+ }
58
+
59
+ return new Promise<void>((resolve, reject) => {
60
+ const socket = dgram.createSocket({ type: "udp4" });
61
+
62
+ socket.once("error", (err: Error) => {
63
+ try { socket.close(); } catch { /* ignore */ }
64
+ reject(err);
65
+ });
66
+
67
+ socket.bind(0, () => {
68
+ try {
69
+ socket.setBroadcast(true);
70
+ } catch {
71
+ // Some platforms don't support setBroadcast — continue anyway
72
+ }
73
+
74
+ let pending = broadcastAddresses.length;
75
+ let failed = false;
76
+
77
+ for (const addr of broadcastAddresses) {
78
+ socket.send(packet, 0, packet.length, 9, addr, (err?: Error) => {
79
+ if (err && !failed) {
80
+ failed = true;
81
+ try { socket.close(); } catch { /* ignore */ }
82
+ reject(err);
83
+ return;
84
+ }
85
+ pending--;
86
+ if (pending === 0) {
87
+ try { socket.close(); } catch { /* ignore */ }
88
+ resolve();
89
+ }
90
+ });
91
+ }
92
+ });
93
+ });
94
+}
95
+
96
+/**
97
+ * Validate a MAC address string.
98
+ */
99
+export function isValidMac(mac: string): boolean {
100
+ return /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/.test(mac.trim());
101
+}
types/index.ts
....@@ -16,9 +16,11 @@
1616 export interface ServerConfig {
1717 host: string;
1818 port: number;
19
+ localHost?: string;
20
+ macAddress?: string;
1921 }
2022
21
-export type ConnectionStatus = "disconnected" | "connecting" | "connected";
23
+export type ConnectionStatus = "disconnected" | "connecting" | "connected" | "compacting";
2224
2325 // --- WebSocket protocol ---
2426
....@@ -34,13 +36,20 @@
3436 content: string;
3537 }
3638
39
+export interface WsImageMessage {
40
+ type: "image";
41
+ imageBase64: string;
42
+ caption: string;
43
+ mimeType: string;
44
+}
45
+
3746 export interface WsCommandMessage {
3847 type: "command";
3948 command: string;
4049 args?: Record<string, unknown>;
4150 }
4251
43
-export type WsOutgoing = WsTextMessage | WsVoiceMessage | WsCommandMessage;
52
+export type WsOutgoing = WsTextMessage | WsVoiceMessage | WsImageMessage | WsCommandMessage;
4453
4554 /** Incoming from watcher to app */
4655 export interface WsIncomingText {
....@@ -91,6 +100,11 @@
91100 message: string;
92101 }
93102
103
+export interface WsIncomingStatus {
104
+ type: "status";
105
+ status: string;
106
+}
107
+
94108 export type WsIncoming =
95109 | WsIncomingText
96110 | WsIncomingVoice
....@@ -98,4 +112,5 @@
98112 | WsIncomingSessions
99113 | WsIncomingSessionSwitched
100114 | WsIncomingSessionRenamed
101
- | WsIncomingError;
115
+ | WsIncomingError
116
+ | WsIncomingStatus;