diff --git a/.gitignore b/.gitignore
index dc11ae93e..383005d19 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,6 +39,10 @@ apps/mobile/generated/
apps/mobile/src/runtime/config/bundled.generated.ios.ts
apps/mobile/src/runtime/config/bundled.generated.android.ts
+# gradle caches inside tracked local expo modules (the app's android/ dir is already ignored)
+apps/mobile/modules/*/android/.gradle/
+apps/mobile/modules/*/android/build/
+
# standalone daemon distribution (pnpm --prod deploy output)
apps/daemon/standalone/
diff --git a/apps/mobile/AGENTS.md b/apps/mobile/AGENTS.md
index 8a79f8a73..860a84961 100644
--- a/apps/mobile/AGENTS.md
+++ b/apps/mobile/AGENTS.md
@@ -1,6 +1,6 @@
# apps/mobile — Expo / React Native client
-Expo + React Native. List/form screens render **`@expo/ui`** (real SwiftUI); **HeroUI Native**
+Expo + React Native. List/form screens render **`@expo/ui`** (SwiftUI on iOS, Jetpack Compose on Android); **HeroUI Native**
covers the RN surfaces that cannot cross over ("What deliberately stays React Native" below).
Reaches the host through the `server` tunnel; business data still travels over `transport` +
`@linkcode/schema`, the same contract as every other client.
@@ -44,6 +44,27 @@ Native. Mobile consumes `@linkcode/ui` only through its **native** components
under Node, suspect this class first and probe the runtime rather than reasoning from the API's
documentation.
+## Native UI boundaries
+
+Platform views share `runtime/` hooks for data and actions. Keep `.ios.tsx` responsible for
+SwiftUI and the Android implementation responsible for Compose; shared routes and runtime must
+not import either platform's UI package. Android hosts use `ThemedHost` so Material colors follow
+the app's appearance preference. Each visible list row must be a direct `LazyColumn` child.
+
+Expo's Compose `Switch` always receives a native callback, even without a JS handler. Wire
+`onCheckedChange` alongside a toggleable row; a tap on the thumb is consumed by the switch.
+Single-choice rows use `selectable` and their parent uses `selectableGroup` for TalkBack.
+
+List-item management actions use SwiftUI `SwipeActions` on iOS (horizontal row swipes) and
+`RowActions` on Android (Compose long-press dropdown menus under `MaterialExpressiveTheme`).
+Keep a row's normal tap for navigation; rows without a primary action may also open the menu on
+tap. Destructive actions belong in these native action surfaces, not permanent trailing buttons.
+Children of `RowActions` must not add their own click handler, which would consume the gesture.
+
+Android question batches open from a compact transcript dock into a Compose modal sheet. Closing
+the sheet preserves drafts; cancelling the request requires confirmation. Keep permission actions
+inline with one-time approval emphasized, and allow the action row to wrap for longer labels.
+
## `@expo/ui` (SwiftUI) — its layout rules are not RN's
Settings, terminal appearance, and connect render a real `Form` inside a `Host` (`style={{flex:1}}`
@@ -80,13 +101,16 @@ only by driving the simulator:
RN→SwiftUI is the supported direction; going back needs `RNHostView`, whose bidirectional nesting is
the very thing 57.0.5 had to fix. So anything whose indispensable part is an RN view cannot cross
over: **sign-in** (`AppleAuthenticationButton` is an RN view and `@expo/ui` has no Sign in with
-Apple), the **conversation surface** — timeline, composer, and the screen holding them (excluded by
-the redesign decision; the composer also rides `react-native-keyboard-controller`), the **terminal
+Apple), the **conversation layout** — virtualized Markdown timeline and controlled multiline input
+with `react-native-keyboard-controller` (its controls, disclosures, prompts, and transcript records
+use SwiftUI / Compose), the **terminal
canvas** (`expo-libghostty`), the **startup splash** (`BrandMark` is a bundled RN image), and the
-**navigation header** (react-navigation). Two smaller losses are accepted rather than worked
+**navigation header** (react-navigation). One smaller loss is accepted rather than worked
around: `Image` takes SF Symbols, asset-catalog names, and local file URIs but **never a remote
-URL**, so the account avatar is an SF Symbol; and the agent brand marks are RN SVG components, so
-thread rows and the new-thread picker name the agent in text instead.
+URL**, so the account avatar is an SF Symbol. The agent brand marks (RN SVG) do cross into rows
+now: the iOS thread row hosts `AgentIcon` through `RNHostView` (`matchContents`, pointer events
+off), and the Android row uses vector-drawable copies of the lobe glyphs in `assets/icons/` —
+but the new-thread picker and selector sheets still name the agent in text.
## Styling & version pins
@@ -161,6 +185,9 @@ thread rows and the new-thread picker name the agent in text instead.
## Build & run
+- Expo SDK 57 requires Xcode 26.4 or newer. Older Xcode can fail while compiling
+ `expo-modules-jsi`'s Swift/C++ bridging annotations before app code is built.
+
- **Dev builds, not Expo Go.** Cloud sign-in needs the real `linkcode://` scheme: production HQ
trusts only `https://linkcode.ai,linkcode://` (`TRUSTED_ORIGINS`), and the `@better-auth/expo`
server plugin auto-trusts `exp://` only under `NODE_ENV=development`, so "Sign in" from Expo Go
@@ -182,6 +209,8 @@ thread rows and the new-thread picker name the agent in text instead.
`scripts.mobile` in `devenv.nix` unsets both groups (and sets `SENTRY_DISABLE_AUTO_UPLOAD`, since
the Sentry Xcode phase otherwise fails the build without org/project credentials). Outside devenv
the shell is already clean and `pnpm -F @linkcode/mobile ios` works as-is.
+ CocoaPods comes from devenv with its own Ruby runtime; a user-installed `pod` using env Ruby
+ breaks when this runner puts `/usr/bin` first and resolves Apple's older system Ruby.
Symptoms are misleading: the first thing to fail is usually the `[RNDeps] Replace React Native
Dependencies` script phase, which is collateral — that script runs fine on its own.
diff --git a/apps/mobile/app.json b/apps/mobile/app.json
index f13eaa364..9324eea55 100644
--- a/apps/mobile/app.json
+++ b/apps/mobile/app.json
@@ -87,6 +87,7 @@
],
"expo-apple-authentication",
"expo-background-task",
+ "expo-font",
"expo-sqlite",
"expo-secure-store",
"expo-status-bar",
diff --git a/apps/mobile/assets/android-icon-foreground.png b/apps/mobile/assets/android-icon-foreground.png
index c3e2ca447..0fb671470 100644
Binary files a/apps/mobile/assets/android-icon-foreground.png and b/apps/mobile/assets/android-icon-foreground.png differ
diff --git a/apps/mobile/assets/android-icon-monochrome.png b/apps/mobile/assets/android-icon-monochrome.png
index 8f5dc150f..ed5e3132f 100644
Binary files a/apps/mobile/assets/android-icon-monochrome.png and b/apps/mobile/assets/android-icon-monochrome.png differ
diff --git a/apps/mobile/assets/icons/add.xml b/apps/mobile/assets/icons/add.xml
new file mode 100644
index 000000000..fb14723ce
--- /dev/null
+++ b/apps/mobile/assets/icons/add.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/apps/mobile/assets/icons/agent-claude-code.xml b/apps/mobile/assets/icons/agent-claude-code.xml
new file mode 100644
index 000000000..ebe5d9b60
--- /dev/null
+++ b/apps/mobile/assets/icons/agent-claude-code.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/apps/mobile/assets/icons/agent-codex.xml b/apps/mobile/assets/icons/agent-codex.xml
new file mode 100644
index 000000000..7ad436a4b
--- /dev/null
+++ b/apps/mobile/assets/icons/agent-codex.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/apps/mobile/assets/icons/agent-opencode.xml b/apps/mobile/assets/icons/agent-opencode.xml
new file mode 100644
index 000000000..6d807e4e0
--- /dev/null
+++ b/apps/mobile/assets/icons/agent-opencode.xml
@@ -0,0 +1,10 @@
+
+
+
diff --git a/apps/mobile/assets/icons/arrow-up.xml b/apps/mobile/assets/icons/arrow-up.xml
new file mode 100644
index 000000000..496043063
--- /dev/null
+++ b/apps/mobile/assets/icons/arrow-up.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/assets/icons/check.xml b/apps/mobile/assets/icons/check.xml
new file mode 100644
index 000000000..935007004
--- /dev/null
+++ b/apps/mobile/assets/icons/check.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/apps/mobile/assets/icons/chevron-left.xml b/apps/mobile/assets/icons/chevron-left.xml
new file mode 100644
index 000000000..ddb20ec23
--- /dev/null
+++ b/apps/mobile/assets/icons/chevron-left.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/assets/icons/chevron-right.xml b/apps/mobile/assets/icons/chevron-right.xml
new file mode 100644
index 000000000..60f1b2f8b
--- /dev/null
+++ b/apps/mobile/assets/icons/chevron-right.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/assets/icons/close.xml b/apps/mobile/assets/icons/close.xml
new file mode 100644
index 000000000..fdfeebfe8
--- /dev/null
+++ b/apps/mobile/assets/icons/close.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/assets/icons/expand-less.xml b/apps/mobile/assets/icons/expand-less.xml
new file mode 100644
index 000000000..e08b770eb
--- /dev/null
+++ b/apps/mobile/assets/icons/expand-less.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/apps/mobile/assets/icons/expand-more.xml b/apps/mobile/assets/icons/expand-more.xml
new file mode 100644
index 000000000..93d859cfe
--- /dev/null
+++ b/apps/mobile/assets/icons/expand-more.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/apps/mobile/assets/icons/more-vert.xml b/apps/mobile/assets/icons/more-vert.xml
new file mode 100644
index 000000000..9c2a0c227
--- /dev/null
+++ b/apps/mobile/assets/icons/more-vert.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/apps/mobile/assets/icons/search.xml b/apps/mobile/assets/icons/search.xml
new file mode 100644
index 000000000..83a9d154c
--- /dev/null
+++ b/apps/mobile/assets/icons/search.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/apps/mobile/assets/icons/shield.xml b/apps/mobile/assets/icons/shield.xml
new file mode 100644
index 000000000..7c9446905
--- /dev/null
+++ b/apps/mobile/assets/icons/shield.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/assets/icons/stop.xml b/apps/mobile/assets/icons/stop.xml
new file mode 100644
index 000000000..5541768c6
--- /dev/null
+++ b/apps/mobile/assets/icons/stop.xml
@@ -0,0 +1 @@
+
diff --git a/apps/mobile/package.json b/apps/mobile/package.json
index 8d64cec35..1e216bb74 100644
--- a/apps/mobile/package.json
+++ b/apps/mobile/package.json
@@ -17,7 +17,7 @@
},
"dependencies": {
"@better-auth/expo": "1.7.0-rc.2",
- "@expo/ui": "57.0.7",
+ "@expo/ui": "57.0.11",
"@gorhom/bottom-sheet": "^5.2.14",
"@linkcode/client-core": "workspace:*",
"@linkcode/common": "workspace:*",
@@ -29,34 +29,35 @@
"@react-native-community/netinfo": "12.0.1",
"@sentry/react-native": "~7.11.0",
"better-auth": "1.7.0-rc.2",
- "expo": "~57.0.4",
+ "expo": "~57.0.14",
"expo-apple-authentication": "~57.0.1",
- "expo-audio": "~57.0.0",
- "expo-background-task": "~57.0.2",
- "expo-camera": "~57.0.1",
- "expo-clipboard": "~57.0.0",
- "expo-constants": "~57.0.3",
+ "expo-audio": "~57.0.3",
+ "expo-background-task": "~57.0.11",
+ "expo-camera": "~57.0.3",
+ "expo-clipboard": "~57.0.1",
+ "expo-constants": "~57.0.12",
"expo-crypto": "~57.0.1",
- "expo-dev-client": "~57.0.5",
- "expo-device": "~57.0.0",
- "expo-document-picker": "~57.0.0",
- "expo-file-system": "~57.0.0",
- "expo-font": "~57.0.0",
- "expo-haptics": "~57.0.0",
- "expo-image-picker": "~57.0.2",
+ "expo-dev-client": "~57.0.13",
+ "expo-device": "~57.0.1",
+ "expo-document-picker": "~57.0.1",
+ "expo-file-system": "~57.0.4",
+ "expo-font": "~57.0.1",
+ "expo-haptics": "~57.0.1",
+ "expo-image-picker": "~57.0.11",
"expo-libghostty": "^0.8.1",
- "expo-linking": "~57.0.2",
- "expo-local-authentication": "~57.0.0",
+ "expo-linking": "~57.0.6",
+ "expo-local-authentication": "~57.0.2",
"expo-network": "~57.0.1",
- "expo-notifications": "~57.0.3",
- "expo-router": "~57.0.4",
- "expo-secure-store": "~57.0.0",
- "expo-sharing": "~57.0.3",
- "expo-sqlite": "~57.0.0",
- "expo-status-bar": "~57.0.0",
- "expo-task-manager": "~57.0.2",
- "expo-updates": "~57.0.6",
- "expo-web-browser": "~57.0.0",
+ "expo-notifications": "~57.0.12",
+ "expo-router": "~57.0.14",
+ "expo-secure-store": "~57.0.1",
+ "expo-sharing": "~57.0.13",
+ "expo-sqlite": "~57.0.1",
+ "expo-status-bar": "~57.0.1",
+ "expo-symbols": "~57.0.2",
+ "expo-task-manager": "~57.0.11",
+ "expo-updates": "~57.0.15",
+ "expo-web-browser": "~57.0.2",
"foxact": "^0.3.10",
"foxts": "^5.9.1",
"heroui-native": "1.0.6",
@@ -64,16 +65,16 @@
"posthog-react-native": "^4.60.0",
"react": "19.2.3",
"react-dom": "19.2.3",
- "react-native": "0.86.0",
+ "react-native": "0.86.2",
"react-native-gesture-handler": "~2.32.0",
"react-native-keyboard-controller": "1.21.9",
- "react-native-reanimated": "4.5.0",
+ "react-native-reanimated": "4.5.1",
"react-native-safe-area-context": "~5.7.0",
- "react-native-screens": "4.25.2",
+ "react-native-screens": "4.26.2",
"react-native-svg": "15.15.4",
"react-native-web": "^0.21.2",
"react-native-webview": "13.16.1",
- "react-native-worklets": "0.10.0",
+ "react-native-worklets": "0.10.1",
"tailwind-merge": "^3.6.0",
"tailwind-variants": "^3.3.0",
"uniwind": "1.10.0",
diff --git a/apps/mobile/src/app/(tabs)/_layout.tsx b/apps/mobile/src/app/(tabs)/_layout.tsx
index 9280c9a34..33598124f 100644
--- a/apps/mobile/src/app/(tabs)/_layout.tsx
+++ b/apps/mobile/src/app/(tabs)/_layout.tsx
@@ -32,12 +32,14 @@ function TabsNavigator(): React.ReactNode {
return (
+ {/* `md` is the Android glyph — without it the converter leaves the icon undefined and the
+ tab renders label-only; `sf` is never read there. */}
-
+
{tThreads('title')}
-
+
{tTerminals('title')}
{/* iOS 26's separated tab-bar slot (the `search` role) carries the focused tab's primary
diff --git a/apps/mobile/src/app/(tabs)/terminals/index.tsx b/apps/mobile/src/app/(tabs)/terminals/index.tsx
index acdd0f672..300375f1e 100644
--- a/apps/mobile/src/app/(tabs)/terminals/index.tsx
+++ b/apps/mobile/src/app/(tabs)/terminals/index.tsx
@@ -1,43 +1,25 @@
-import {
- Button,
- ContentUnavailableView,
- Form,
- Host,
- ProgressView,
- Section,
- Text,
-} from '@expo/ui/swift-ui';
-import { foregroundStyle, refreshable } from '@expo/ui/swift-ui/modifiers';
-import { useLinkCodeClient } from '@linkcode/client-core';
-import type { TerminalMetadata } from '@linkcode/schema';
-import { repositoryLabel } from '@linkcode/ui/native';
-import { NavigationRow } from '@mobile/components/form/navigation-row';
import { HostClientGate } from '@mobile/components/host/host-client-gate';
-import { useHostMenuItems } from '@mobile/components/host/use-host-menu-items';
+import { useHostHeaderOptions } from '@mobile/components/host/use-host-header-options';
import type { PrimaryAction } from '@mobile/components/shell/primary-action';
import { usePrimaryAction } from '@mobile/components/shell/primary-action';
+import { PrimaryActionFab } from '@mobile/components/shell/primary-action-fab';
import { VISIBLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
import { useTrailingActions } from '@mobile/components/shell/use-trailing-actions';
-import { NewTerminalSheet } from '@mobile/components/terminal/new-terminal-sheet';
+import { TerminalsScreen } from '@mobile/components/terminal/terminals-screen';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import { useHostConnection } from '@mobile/runtime/host-connection';
-import { Stack, useFocusEffect, useRouter } from 'expo-router';
-import { extractErrorMessage } from 'foxts/extract-error-message';
+import { Stack } from 'expo-router';
import { PlusIcon } from 'lucide-react-native';
-import { useCallback, useState } from 'react';
-import { Platform, View } from 'react-native';
+import { useState } from 'react';
+import { View } from 'react-native';
import { useTranslations } from 'use-intl';
-const INITIAL_TERMINAL_SIZE = { cols: 80, rows: 24 };
-
-const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
-const SUPPORTS_CONTENT_UNAVAILABLE_VIEW =
- Platform.OS === 'ios' && Number.parseInt(Platform.Version, 10) >= 17;
-
/** Header above the gate, for the same reason as the threads tab: the host switcher has to stay
* reachable when the host is not. */
export default function TerminalsRoute(): React.ReactNode {
const t = useTranslations('mobile.terminals');
- const hostMenuItems = useHostMenuItems();
+ const hostHeaderOptions = useHostHeaderOptions();
+ const palette = useNativePalette();
const connection = useHostConnection();
const [sheetOpen, setSheetOpen] = useState(false);
@@ -56,12 +38,12 @@ export default function TerminalsRoute(): React.ReactNode {
// The flex container is load-bearing: a SwiftUI host left as the screen's direct child is
// proposed the whole window and paints straight over the navigation header.
return (
-
+
hostMenuItems,
+ ...hostHeaderOptions,
...trailingActions,
}}
/>
@@ -72,159 +54,7 @@ export default function TerminalsRoute(): React.ReactNode {
onSheetOpenChange={setSheetOpen}
/>
+
);
}
-
-/** Host terminal inbox: attach to a running PTY or start a new one on the host. */
-function TerminalsScreen({
- sheetOpen,
- onSheetOpenChange,
-}: {
- sheetOpen: boolean;
- onSheetOpenChange: (open: boolean) => void;
-}): React.ReactNode {
- const t = useTranslations('mobile.terminals');
- const router = useRouter();
- const client = useLinkCodeClient();
- const [terminals, setTerminals] = useState([]);
- const [loading, setLoading] = useState(true);
- const [loadError, setLoadError] = useState(null);
- const [createError, setCreateError] = useState(null);
- const [creating, setCreating] = useState(false);
-
- const load = useCallback(
- async () => (await client.listTerminals()).sort((a, b) => b.createdAt - a.createdAt),
- [client],
- );
-
- useFocusEffect(
- useCallback(() => {
- let active = true;
- setLoadError(null);
- void load()
- .then((nextTerminals) => {
- if (active) setTerminals(nextTerminals);
- })
- .catch((error_: unknown) => {
- if (active) setLoadError(extractErrorMessage(error_, false) ?? 'Unknown error');
- })
- .finally(() => {
- if (active) setLoading(false);
- });
- return () => {
- active = false;
- };
- }, [load]),
- );
-
- // Drives SwiftUI's own pull-to-refresh, which keeps its spinner until this resolves.
- const onRefresh = async () => {
- setLoadError(null);
- try {
- setTerminals(await load());
- } catch (error_) {
- setLoadError(extractErrorMessage(error_, false) ?? 'Unknown error');
- }
- };
-
- const openTerminal = (terminalId: string, takeControl = false) => {
- const query = takeControl ? '?takeover=1' : '';
- router.push(`/terminal/${encodeURIComponent(terminalId)}${query}`);
- };
-
- const onCreate = async (cwd: string): Promise => {
- if (creating) return false;
- setCreating(true);
- setCreateError(null);
- try {
- const terminalId = await client.openTerminal({
- ...INITIAL_TERMINAL_SIZE,
- cwd: cwd || undefined,
- });
- client.detachTerminal(terminalId);
- onSheetOpenChange(false);
- openTerminal(terminalId, true);
- return true;
- } catch (error_) {
- setCreateError(extractErrorMessage(error_, false) ?? 'Unknown error');
- return false;
- } finally {
- setCreating(false);
- }
- };
-
- return (
- <>
- {/* Form needs the viewport as its proposed size, otherwise it collapses to its content. */}
-
- {loading ? (
-
- ) : loadError && terminals.length === 0 ? (
-
- ) : terminals.length === 0 ? (
- SUPPORTS_CONTENT_UNAVAILABLE_VIEW ? (
-
- ) : (
-
- )
- ) : (
-
- )}
-
- {
- if (!open) setCreateError(null);
- onSheetOpenChange(open);
- }}
- creating={creating}
- error={createError}
- onCreate={onCreate}
- />
- >
- );
-}
diff --git a/apps/mobile/src/app/(tabs)/threads/index.tsx b/apps/mobile/src/app/(tabs)/threads/index.tsx
index 052d99180..3c90224c6 100644
--- a/apps/mobile/src/app/(tabs)/threads/index.tsx
+++ b/apps/mobile/src/app/(tabs)/threads/index.tsx
@@ -1,54 +1,38 @@
-import {
- Form,
- Host,
- ProgressView,
- Section,
- Button as UIButton,
- Text as UIText,
-} from '@expo/ui/swift-ui';
-import { useSessions } from '@linkcode/client-core';
-import type { SessionInfo } from '@linkcode/schema';
-import type { ThreadGroup } from '@linkcode/ui/native';
-import {
- AGENT_LABELS,
- groupThreadsByWorkspace,
- repositoryLabel,
- withoutAutomationSessions,
-} from '@linkcode/ui/native';
-import { SECONDARY } from '@mobile/components/form/styles';
+import { useSearchHeaderOptions } from '@mobile/components/form/use-search-header-options';
import { HostClientGate } from '@mobile/components/host/host-client-gate';
-import { ThreadList } from '@mobile/components/host/thread-list/thread-list';
-import { useHostMenuItems } from '@mobile/components/host/use-host-menu-items';
+import { ThreadsScreen } from '@mobile/components/host/threads-screen';
+import { useHostHeaderOptions } from '@mobile/components/host/use-host-header-options';
import type { PrimaryAction } from '@mobile/components/shell/primary-action';
import { usePrimaryAction } from '@mobile/components/shell/primary-action';
+import { PrimaryActionFab } from '@mobile/components/shell/primary-action-fab';
import { VISIBLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
import { useTrailingActions } from '@mobile/components/shell/use-trailing-actions';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import { useHostConnection } from '@mobile/runtime/host-connection';
-import { useWorkspaces } from '@mobile/runtime/use-workspaces';
import { Stack, useRouter } from 'expo-router';
import { SquarePenIcon } from 'lucide-react-native';
import { useCallback, useState } from 'react';
-import { View } from 'react-native';
+import { Platform, View } from 'react-native';
import { useTranslations } from 'use-intl';
-/** Taken from the search bar itself: RN's own replacement for the event it declares carries no text. */
-type SearchBarChangeEvent = Parameters<
- NonNullable['onChangeText']>
->[0];
-
-/** The title a thread is listed and searched under — the same fallback the row renders. */
-function threadTitle(session: SessionInfo): string {
- return session.title ?? `${AGENT_LABELS[session.kind]} in ${repositoryLabel(session.cwd)}`;
-}
-
/** The header outlives the connection: it carries the host switcher, which is the way out of a host
* that cannot be reached, so it is mounted above the gate rather than inside it. New-thread needs a
* client, so its entry points are dropped until the connection is ready. */
export default function ThreadsRoute(): React.ReactNode {
const t = useTranslations('mobile.sessions');
+ const tChat = useTranslations('mobile.chat');
const router = useRouter();
- const hostMenuItems = useHostMenuItems();
+ const hostHeaderOptions = useHostHeaderOptions();
+ const palette = useNativePalette();
const connection = useHostConnection();
+ // Android-only affordance: iOS search lives in the screen's native search bar instead.
+ const [searchOpen, setSearchOpen] = useState(false);
+ // Toggling the search resets the filter: the field remounts empty, so the query must too.
+ const [query, setQuery] = useState('');
+ const closeSearch = useCallback(() => {
+ setSearchOpen(false);
+ setQuery('');
+ }, []);
const primaryAction: PrimaryAction | null =
connection?.status === 'ready'
@@ -60,108 +44,44 @@ export default function ThreadsRoute(): React.ReactNode {
}
: null;
usePrimaryAction('threads', primaryAction);
- const trailingActions = useTrailingActions(primaryAction);
+ const trailingActions = useTrailingActions(primaryAction, {
+ ...(Platform.OS === 'android' && {
+ onSearchPress() {
+ setQuery('');
+ setSearchOpen((open) => !open);
+ },
+ }),
+ });
+ const searchHeaderOptions = useSearchHeaderOptions({
+ open: searchOpen,
+ placeholder: t('searchPlaceholder'),
+ closeLabel: tChat('cancel'),
+ onQueryChange: setQuery,
+ onClose: closeSearch,
+ });
return (
-
+
hostMenuItems,
+ ...hostHeaderOptions,
...trailingActions,
+ // The search view swaps the top app bar's content, replacing the switcher and actions;
+ // the toolbar itself stays mounted so the header never changes height (no flicker).
+ ...searchHeaderOptions,
}}
/>
-
+
+
);
}
-
-/** Threads inbox: sessions grouped by workspace (project) under collapsible headers, with the
- * native search bar stacked below the navigation bar. Empty workspace groups are hidden — the
- * new-thread page is where they surface. */
-function ThreadsScreen(): React.ReactNode {
- const t = useTranslations('mobile.sessions');
- const router = useRouter();
- const { sessions, refresh, loading } = useSessions();
- const { workspaces, refresh: refreshWorkspaces } = useWorkspaces();
-
- const [query, setQuery] = useState('');
-
- // Stable so the search bar's options object survives a keystroke without re-registering.
- const onSearchChange = useCallback(
- (event: SearchBarChangeEvent) => setQuery(event.nativeEvent.text),
- [],
- );
-
- const needle = query.trim().toLowerCase();
- const groups = groupThreadsByWorkspace(withoutAutomationSessions(sessions), workspaces).reduce<
- ThreadGroup[]
- >((kept, group) => {
- const matched =
- needle === ''
- ? group.sessions
- : group.sessions.filter((session) => threadTitle(session).toLowerCase().includes(needle));
- if (matched.length > 0) kept.push({ ...group, sessions: matched });
- return kept;
- }, []);
-
- const groupLabel = (group: ThreadGroup): string => {
- if (group.isChat) return t('chats');
- if (!group.workspace) return t('otherThreads');
- return group.workspace.name ?? repositoryLabel(group.workspace.cwd);
- };
-
- const onRefresh = async () => {
- await Promise.all([refresh(), refreshWorkspaces()]);
- };
-
- return (
- <>
- {/* `stacked` keeps the field below the inline title instead of moving into the iOS 26 toolbar. */}
-
- {/* The list needs the viewport as its proposed size, otherwise SwiftUI collapses it. */}
-
- {loading ? (
-
- ) : groups.length === 0 ? (
- // A query that matched nothing is not an empty inbox: saying "no threads yet" there
- // reads as though the existing threads were lost, and offering to start one is no
- // remedy for a bad search.
-
- ) : (
- router.push(`/session/${sessionId}`)}
- onRefresh={onRefresh}
- />
- )}
-
- >
- );
-}
diff --git a/apps/mobile/src/app/account.tsx b/apps/mobile/src/app/account.tsx
index fb11505b1..b3ebf289f 100644
--- a/apps/mobile/src/app/account.tsx
+++ b/apps/mobile/src/app/account.tsx
@@ -1,15 +1,10 @@
-import { Button, Form, Host, ProgressView, Section } from '@expo/ui/swift-ui';
-import { DeleteAccountSection } from '@mobile/components/account/delete-account-section';
-import { DevicesSection } from '@mobile/components/account/devices-section';
-import { ProfileRow } from '@mobile/components/account/profile-row';
+import { AccountScreen } from '@mobile/components/account/account-screen';
import { VISIBLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
-import { signOutOfCloud, useCloudAccount } from '@mobile/runtime/cloud/account';
+import { useCloudAccount } from '@mobile/runtime/cloud/account';
import { Redirect, Stack } from 'expo-router';
-import { Alert } from 'react-native';
import { useTranslations } from 'use-intl';
-/** Account screen: profile, the account's device registry, and sign-out. */
-export default function AccountScreen(): React.ReactNode {
+export default function AccountRoute(): React.ReactNode {
const t = useTranslations('mobile.account');
const account = useCloudAccount();
@@ -18,29 +13,7 @@ export default function AccountScreen(): React.ReactNode {
return (
<>
- {/* Form needs the viewport as its proposed size, otherwise it collapses to its content. */}
-
-
-
+
>
);
}
diff --git a/apps/mobile/src/app/connect.tsx b/apps/mobile/src/app/connect.tsx
index e6199a053..bd7eb7e14 100644
--- a/apps/mobile/src/app/connect.tsx
+++ b/apps/mobile/src/app/connect.tsx
@@ -1,18 +1,10 @@
-import { Form, Host } from '@expo/ui/swift-ui';
-import { DiscoveredHostsSection } from '@mobile/components/connect/discovered-hosts-section';
-import { MyMachinesSection } from '@mobile/components/connect/my-machines-section';
-import { SavedHostsSection } from '@mobile/components/connect/saved-hosts-section';
-import { SignInSection } from '@mobile/components/connect/sign-in-section';
+import { ConnectScreen } from '@mobile/components/connect/connect-screen';
import { VISIBLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
-import { useCloudAccount } from '@mobile/runtime/cloud/account';
-import { useHostRegistryStore } from '@mobile/stores/host-store';
import { Stack } from 'expo-router';
import { useTranslations } from 'use-intl';
-export default function ConnectScreen(): React.ReactNode {
+export default function ConnectRoute(): React.ReactNode {
const t = useTranslations('mobile.connect');
- const account = useCloudAccount();
- const hosts = useHostRegistryStore((state) => state.hosts);
return (
<>
@@ -22,20 +14,7 @@ export default function ConnectScreen(): React.ReactNode {
title: t('title'),
}}
/>
- {/* Form needs the viewport as its proposed size, otherwise it collapses to its content. */}
-
-
-
+
>
);
}
diff --git a/apps/mobile/src/app/index.tsx b/apps/mobile/src/app/index.tsx
index e8de4c245..11c64fe31 100644
--- a/apps/mobile/src/app/index.tsx
+++ b/apps/mobile/src/app/index.tsx
@@ -1,10 +1,8 @@
-import { BrandMark } from '@mobile/components/shell/brand-mark';
+import { AppLoadingScreen } from '@mobile/components/shell/app-loading-screen';
import { useCloudAccount } from '@mobile/runtime/cloud/account';
import { resolveStartupTarget } from '@mobile/runtime/startup';
import { useHostRegistryHydrated, useHostRegistryStore } from '@mobile/stores/host-store';
import { Redirect } from 'expo-router';
-import { Spinner } from 'heroui-native';
-import { View } from 'react-native';
/**
* Startup router: waits for the persisted registry and the account state,
@@ -19,12 +17,7 @@ export default function StartupScreen() {
// Only first-run routing waits on account state — LAN/direct users with
// saved hosts must not block on a slow or offline cloud session check.
if (!hydrated || (hosts.length === 0 && account.status === 'loading')) {
- return (
-
-
-
-
- );
+ return ;
}
const target = resolveStartupTarget({
diff --git a/apps/mobile/src/app/new-thread.tsx b/apps/mobile/src/app/new-thread.tsx
index e30fe5230..2cfa8b4f0 100644
--- a/apps/mobile/src/app/new-thread.tsx
+++ b/apps/mobile/src/app/new-thread.tsx
@@ -1,13 +1,9 @@
-import { Composer } from '@mobile/components/conversation/composer';
import { HostClientGate } from '@mobile/components/host/host-client-gate';
-import { AgentSelectorChip, ApprovalChip } from '@mobile/components/host/new-thread/draft-tools';
-import { ProjectRow } from '@mobile/components/host/new-thread/project-row';
+import { NewThreadScreen } from '@mobile/components/host/new-thread/new-thread-screen';
import { VISIBLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
-import { useNewThreadDraft } from '@mobile/runtime/use-new-thread-draft';
-import { Stack, useRouter } from 'expo-router';
-import { noop } from 'foxact/noop';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { Stack } from 'expo-router';
import { View } from 'react-native';
-import { KeyboardStickyView } from 'react-native-keyboard-controller';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
/** Composer-first new-thread page, the mobile shape of the desktop draft surface: the start
@@ -15,9 +11,13 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
* starts on the host with the prompt riding behind it. A root push, so it covers the tab bar. */
export default function NewThreadRoute(): React.ReactNode {
const insets = useSafeAreaInsets();
+ const palette = useNativePalette();
return (
-
+
{/* Title-less on purpose: the composer says everything, and the bare back chevron keeps
the page reading as a sheet of options rather than a destination. */}
@@ -27,32 +27,3 @@ export default function NewThreadRoute(): React.ReactNode {
);
}
-
-function NewThreadScreen(): React.ReactNode {
- const router = useRouter();
- const { text, setText, start, creating, sendBlocked, error, project, approval, selector } =
- useNewThreadDraft((sessionId) => router.replace(`/session/${sessionId}`));
-
- return (
- <>
-
-
-
- {
- void start(text);
- }}
- onStop={noop}
- isRunning={false}
- disabled={creating}
- sendBlocked={sendBlocked}
- error={error}
- tools={}
- trailing={}
- />
-
- >
- );
-}
diff --git a/apps/mobile/src/app/session/[sessionId].tsx b/apps/mobile/src/app/session/[sessionId].tsx
index f5dca6630..b9374e551 100644
--- a/apps/mobile/src/app/session/[sessionId].tsx
+++ b/apps/mobile/src/app/session/[sessionId].tsx
@@ -3,6 +3,7 @@ import type { SessionId, ToolCall } from '@linkcode/schema';
import { SessionIdSchema } from '@linkcode/schema';
import {
AGENT_LABELS,
+ conversationFlowItems,
EFFORT_OPTIONS_BY_ID,
EmptyState,
effortOptionsForModel,
@@ -22,20 +23,18 @@ import {
import { TimelineItem } from '@mobile/components/conversation/timeline-item';
import { ToolDetailSheet } from '@mobile/components/conversation/tool-detail-sheet/tool-detail-sheet';
import { HostClientGate } from '@mobile/components/host/host-client-gate';
-import { USES_IOS_26_NAVIGATION } from '@mobile/components/shell/ios-26-navigation';
+import { HeaderMenuButton } from '@mobile/components/shell/header-menu-button';
import { VISIBLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import { useAccountModels } from '@mobile/runtime/use-account-models';
import { useSeededConversation } from '@mobile/runtime/use-seeded-conversation';
import { useSessionActions } from '@mobile/runtime/use-session-actions';
import { useSessionAutoResume } from '@mobile/runtime/use-session-auto-resume';
import * as Clipboard from 'expo-clipboard';
import { Stack, useLocalSearchParams, useRouter } from 'expo-router';
-import { useHeaderHeight } from 'expo-router/react-navigation';
import { noop } from 'foxact/noop';
-import { useThemeColor } from 'heroui-native';
-import { EllipsisIcon } from 'lucide-react-native';
import { useEffect, useRef, useState } from 'react';
-import { Alert, FlatList, Pressable, View } from 'react-native';
+import { FlatList, View } from 'react-native';
import { KeyboardStickyView } from 'react-native-keyboard-controller';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTranslations } from 'use-intl';
@@ -59,8 +58,7 @@ function SessionScreen(): React.ReactNode {
const tChat = useTranslations('mobile.chat');
const tSettings = useTranslations('mobile.settings');
const insets = useSafeAreaInsets();
- const headerHeight = useHeaderHeight();
- const muted = useThemeColor('muted');
+ const palette = useNativePalette();
const router = useRouter();
const { sessionId: rawSessionId, autoResume } = useLocalSearchParams<{
sessionId: string;
@@ -85,9 +83,6 @@ function SessionScreen(): React.ReactNode {
const actions = useSessionActions(sessionId, conversation.status);
const { stop } = useSessionAutoResume(sessionId, session?.status, autoResumeSuppressed);
const [openToolCallId, setOpenToolCallId] = useState(null);
- // Measured height of the floating composer block, fed back to the list as its bottom inset so
- // resting content clears the card while scrolling still flows under the glass.
- const [dockHeight, setDockHeight] = useState(0);
const title = session
? (session.title ?? `${AGENT_LABELS[session.kind]} in ${repositoryLabel(session.cwd)}`)
@@ -133,27 +128,23 @@ function SessionScreen(): React.ReactNode {
if (sessionId) void Clipboard.setStringAsync(sessionId);
};
- // Android fallback only — native bar items are iOS-only, so the menu degrades to an alert.
- const showMenu = (): void => {
- if (!sessionId) return;
- Alert.alert(title, undefined, [
- { text: tChat('stopThread'), style: 'destructive', onPress: stopThread },
- { text: tChat('copyThreadId'), onPress: copyThreadId },
- { text: tChat('cancel'), style: 'cancel' },
- ]);
- };
-
// Inverted list: index 0 renders at the visual bottom, so newest items pin there.
- const reversed = [...conversation.items].reverse();
+ const reversed = conversationFlowItems(conversation.items).reverse();
return (
,
...(process.env.EXPO_OS === 'ios'
@@ -185,14 +176,18 @@ function SessionScreen(): React.ReactNode {
}
: {
headerRight: () => (
-
-
-
+
),
}),
}}
@@ -212,29 +207,11 @@ function SessionScreen(): React.ReactNode {
onPressTool={(toolCall) => setOpenToolCallId(toolCall.toolCallId)}
/>
)}
- ListFooterComponent={
- process.env.EXPO_OS === 'ios' ? : null
- }
- // Inverted list: the header renders at the visual bottom — the clearance that keeps
- // resting content out from under the floating composer.
- ListHeaderComponent={
- USES_IOS_26_NAVIGATION && dockHeight > 0 ? (
-
- ) : null
- }
contentContainerStyle={{ paddingHorizontal: 16, paddingVertical: 12, gap: 12 }}
className="flex-1"
/>
)}
- {/* Sticky rather than an avoiding view: the inverted list already pins to the bottom, so
- the composer only has to ride the keyboard instead of resizing the whole screen. On
- iOS 26 the block floats over the list so content scrolls under the glass. */}
- setDockHeight(event.nativeEvent.layout.height)}
- >
+
;
if (appleAvailable === null || account.status === 'loading') {
- return (
-
-
-
-
- );
+ return ;
}
const run = async (flow: () => Promise) => {
@@ -153,8 +115,8 @@ export default function SignInScreen() {
return (
LinkCode
{t('tagline')}
@@ -189,16 +150,16 @@ export default function SignInScreen() {
{busy ? (
) : failed ? (
{t('error')}
@@ -222,42 +183,22 @@ export default function SignInScreen() {
}}
/>
) : null}
-
-
+ router.push('/connect')}
- >
-
- {t('skip')}
-
-
+ />
diff --git a/apps/mobile/src/app/terminal-appearance.tsx b/apps/mobile/src/app/terminal-appearance.tsx
index 65fa7d439..ce1099fa1 100644
--- a/apps/mobile/src/app/terminal-appearance.tsx
+++ b/apps/mobile/src/app/terminal-appearance.tsx
@@ -1,118 +1,15 @@
-import {
- Circle,
- Form,
- Host,
- HStack,
- Picker,
- Section,
- Text,
- VStack,
- ZStack,
-} from '@expo/ui/swift-ui';
-import {
- font,
- foregroundStyle,
- frame,
- pickerStyle,
- strokeBorder,
- tag,
-} from '@expo/ui/swift-ui/modifiers';
+import { TerminalAppearanceScreen } from '@mobile/components/settings/terminal-appearance-screen';
import { VISIBLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
-import {
- resolveTerminalTheme,
- TERMINAL_COLOR_SCHEMES,
- TERMINAL_FONT_SIZES,
- useTerminalPrefsStore,
-} from '@mobile/stores/terminal-prefs-store';
import { Stack } from 'expo-router';
import { useTranslations } from 'use-intl';
-const SWATCH = 24;
-
-const HIERARCHICAL_SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
-
-/** Renders a theme's own colours so the row previews what it selects. `auto` has no theme
- * of its own — it defers to ghostty's defaults — so it shows a neutral placeholder. */
-function ThemeSwatch({ theme }: { theme?: { background?: string; foreground?: string } }) {
- return (
-
-
- {theme?.foreground ? (
-
- a
-
- ) : null}
-
- );
-}
-
-/** Client-side terminal appearance: font size and color scheme. */
-export default function TerminalAppearanceScreen(): React.ReactNode {
+export default function TerminalAppearanceRoute(): React.ReactNode {
const t = useTranslations('mobile.terminalAppearance');
- const fontSize = useTerminalPrefsStore((state) => state.fontSize);
- const colorScheme = useTerminalPrefsStore((state) => state.colorScheme);
- const setFontSize = useTerminalPrefsStore((state) => state.setFontSize);
- const setColorScheme = useTerminalPrefsStore((state) => state.setColorScheme);
return (
<>
-
-
-
+
>
);
}
diff --git a/apps/mobile/src/app/terminal/[terminalId].tsx b/apps/mobile/src/app/terminal/[terminalId].tsx
index 2e4457248..2d42a0fca 100644
--- a/apps/mobile/src/app/terminal/[terminalId].tsx
+++ b/apps/mobile/src/app/terminal/[terminalId].tsx
@@ -1,11 +1,12 @@
import { TerminalIdSchema } from '@linkcode/schema';
+import { ActionButton } from '@mobile/components/form/action-button';
import { HostClientGate } from '@mobile/components/host/host-client-gate';
import TerminalRenderer from '@mobile/components/terminal/terminal-renderer';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import { useTerminalSession } from '@mobile/runtime/use-terminal-session';
import { resolveTerminalTheme, useTerminalPrefsStore } from '@mobile/stores/terminal-prefs-store';
import { useLocalSearchParams, useRouter } from 'expo-router';
-import { Button, Chip, Spinner } from 'heroui-native';
-import { Text, View } from 'react-native';
+import { ActivityIndicator, Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { useTranslations } from 'use-intl';
@@ -24,6 +25,7 @@ function TerminalScreen(): React.ReactNode {
const t = useTranslations('mobile.terminal');
const router = useRouter();
const insets = useSafeAreaInsets();
+ const palette = useNativePalette();
const params = useLocalSearchParams<{ terminalId: string; takeover?: string }>();
const parsed = TerminalIdSchema.safeParse(params.terminalId);
const terminalId = parsed.success ? parsed.data : null;
@@ -55,49 +57,66 @@ function TerminalScreen(): React.ReactNode {
return (
-
+
-
+
{terminal?.cwd ?? t('title')}
{status === 'ready' ? (
-
- {canControl ? t('controlling') : t('readOnly')}
-
+
+ {canControl ? t('controlling') : t('readOnly')}
+
) : null}
{truncated ? (
- {t('truncated')}
+
+ {t('truncated')}
+
) : null}
{error ? (
-
+
{t('error', { error })}
) : null}
{exit ? (
-
+
{exit.code === null ? t('exitedSignal') : t('exited', { code: exit.code })}
) : null}
{status === 'attaching' ? (
-
- {t('attaching')}
+
+
+ {t('attaching')}
+
) : status === 'error' ? (
-
+
) : (
{canControl ? (
-
+
+
+
) : terminal?.managed ? (
-
+
{t('managedReadOnly')}
) : (
-
+
+
+
)}
) : null}
diff --git a/apps/mobile/src/assets.d.ts b/apps/mobile/src/assets.d.ts
index 7f063b4ca..16123cab9 100644
--- a/apps/mobile/src/assets.d.ts
+++ b/apps/mobile/src/assets.d.ts
@@ -2,3 +2,9 @@ declare module '*.woff2' {
const url: string;
export default url;
}
+
+// Android XML vector drawables, served as Metro assets for the Compose `Icon` component.
+declare module '*.xml' {
+ const assetId: number;
+ export default assetId;
+}
diff --git a/apps/mobile/src/components/account/account-screen.ios.tsx b/apps/mobile/src/components/account/account-screen.ios.tsx
new file mode 100644
index 000000000..a41ac800f
--- /dev/null
+++ b/apps/mobile/src/components/account/account-screen.ios.tsx
@@ -0,0 +1,41 @@
+import { Button, Form, Host, Section } from '@expo/ui/swift-ui';
+import { DeleteAccountSection } from '@mobile/components/account/delete-account-section';
+import { DevicesSection } from '@mobile/components/account/devices-section';
+import { ProfileRow } from '@mobile/components/account/profile-row';
+import { LoadingView } from '@mobile/components/form/loading-view.ios';
+import { signOutOfCloud, useCloudAccount } from '@mobile/runtime/cloud/account';
+import { Alert } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+/** Account body: profile, the account's device registry, and sign-out. The route shell owns the
+ * signed-out redirect; a transient non-signed-in status renders as loading. */
+export function AccountScreen(): React.ReactNode {
+ const t = useTranslations('mobile.account');
+ const account = useCloudAccount();
+
+ return (
+ // Form needs the viewport as its proposed size, otherwise it collapses to its content.
+
+ {account.status === 'signed-in' ? (
+
+ ) : (
+
+ )}
+
+ );
+}
diff --git a/apps/mobile/src/components/account/account-screen.tsx b/apps/mobile/src/components/account/account-screen.tsx
new file mode 100644
index 000000000..244a15381
--- /dev/null
+++ b/apps/mobile/src/components/account/account-screen.tsx
@@ -0,0 +1,45 @@
+import { ListItem, Text } from '@expo/ui/jetpack-compose';
+import { clickable } from '@expo/ui/jetpack-compose/modifiers';
+import { DeleteAccountSection } from '@mobile/components/account/delete-account-section';
+import { DevicesSection } from '@mobile/components/account/devices-section';
+import { ProfileRow } from '@mobile/components/account/profile-row';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { FormList } from '@mobile/components/form/list.android';
+import { LoadingView } from '@mobile/components/form/loading-view.android';
+import { FormSection } from '@mobile/components/form/section.android';
+import { signOutOfCloud, useCloudAccount } from '@mobile/runtime/cloud/account';
+import { Alert } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+/** Android account body, mirroring `account-screen.ios.tsx`. Sign-out is a plain error-colored
+ * row — MD3's shape for a destructive entry in a settings list. */
+export function AccountScreen(): React.ReactNode {
+ const t = useTranslations('mobile.account');
+ const colors = useAppMaterialColors();
+ const account = useCloudAccount();
+
+ if (account.status !== 'signed-in') return ;
+
+ return (
+
+
+
+
+
+
+ {
+ void signOutOfCloud().catch(() => Alert.alert(t('signOutError')));
+ }),
+ ]}
+ >
+
+ {t('signOut')}
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/account/delete-account-section.ios.tsx b/apps/mobile/src/components/account/delete-account-section.ios.tsx
new file mode 100644
index 000000000..a81e3a0c0
--- /dev/null
+++ b/apps/mobile/src/components/account/delete-account-section.ios.tsx
@@ -0,0 +1,25 @@
+import { Button, Section } from '@expo/ui/swift-ui';
+import { disabled } from '@expo/ui/swift-ui/modifiers';
+import { useDeleteAccount } from '@mobile/runtime/use-delete-account';
+import { useTranslations } from 'use-intl';
+
+/**
+ * Permanent, in-app account deletion (App Store Guideline 5.1.1(v)). Its own
+ * Section, below Sign out, `Button role="destructive"` — not hidden behind
+ * any secondary menu, matching `DevicesSection`'s destructive-row precedent.
+ */
+export function DeleteAccountSection(): React.ReactNode {
+ const t = useTranslations('mobile.account');
+ const { busy, confirmDelete } = useDeleteAccount();
+
+ return (
+
+ );
+}
diff --git a/apps/mobile/src/components/account/delete-account-section.tsx b/apps/mobile/src/components/account/delete-account-section.tsx
index aee324d1f..afbeeeada 100644
--- a/apps/mobile/src/components/account/delete-account-section.tsx
+++ b/apps/mobile/src/components/account/delete-account-section.tsx
@@ -1,89 +1,29 @@
-import { Button, Section } from '@expo/ui/swift-ui';
-import { disabled } from '@expo/ui/swift-ui/modifiers';
-import { deleteAccount, runAccountDeletionTeardown } from '@mobile/runtime/cloud/deletion';
-import { useState } from 'react';
-import { Alert } from 'react-native';
+import { ListItem, Text } from '@expo/ui/jetpack-compose';
+import { clickable } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { FormSection } from '@mobile/components/form/section.android';
+import { useDeleteAccount } from '@mobile/runtime/use-delete-account';
import { useTranslations } from 'use-intl';
-/**
- * Permanent, in-app account deletion (App Store Guideline 5.1.1(v)). Its own
- * Section, below Sign out, `Button role="destructive"` — not hidden behind
- * any secondary menu, matching `DevicesSection`'s destructive-row precedent.
- */
+/** Android delete row. Compose has no button `role`, so the label uses the error color. */
export function DeleteAccountSection(): React.ReactNode {
const t = useTranslations('mobile.account');
- const [busy, setBusy] = useState(false);
-
- const failureMessage = (code: string | undefined): string => {
- if (code === 'ACCOUNT_DELETION_EMERGENCY_AUDIT_HOLD') return t('deleteEmergencyHold');
- if (code === 'ACCOUNT_DELETION_SOLE_ORGANIZATION_OWNER') return t('deleteSoleOwner');
- return t('deleteFailed');
- };
-
- const run = async () => {
- setBusy(true);
- try {
- const outcome = await deleteAccount();
- if (outcome.kind === 'reauthentication-failed') {
- Alert.alert(t('deleteReauthenticationFailed'));
- return;
- }
- if (outcome.kind === 'apple-device-required') {
- Alert.alert(t('deleteAppleDeviceRequired'));
- return;
- }
- if (outcome.kind === 'account-mismatch') {
- Alert.alert(t('deleteAccountMismatch'));
- return;
- }
- if (outcome.kind === 'unknown') {
- Alert.alert(t('deleteUnknown'));
- return;
- }
- if (outcome.kind === 'failed') {
- Alert.alert(failureMessage(outcome.code));
- return;
- }
-
- // Both remaining outcomes (`pending` and `completed`) mean the server
- // already accepted the deletion — local teardown runs regardless.
- await runAccountDeletionTeardown();
- if (outcome.kind === 'pending') {
- Alert.alert(t('deletePending'));
- return;
- }
- // A failed revocation still leaves deletion successful and needs manual Apple follow-up.
- if (outcome.authorizationRevocation === 'failed') {
- Alert.alert(t('deleteRevocationFailedTitle'), t('deleteRevocationFailedMessage'));
- } else {
- Alert.alert(t('deleteCompleted'));
- }
- } finally {
- setBusy(false);
- }
- };
-
- const confirmDelete = () => {
- Alert.alert(t('deleteTitle'), t('deleteMessage'), [
- { text: t('deleteCancel'), style: 'cancel' },
- {
- text: t('deleteConfirm'),
- style: 'destructive',
- onPress() {
- void run();
- },
- },
- ]);
- };
+ const colors = useAppMaterialColors();
+ const { busy, confirmDelete } = useDeleteAccount();
return (
-
+
+ {
+ if (!busy) confirmDelete();
+ }),
+ ]}
+ >
+
+ {t('deleteAccount')}
+
+
+
);
}
diff --git a/apps/mobile/src/components/account/devices-section.ios.tsx b/apps/mobile/src/components/account/devices-section.ios.tsx
new file mode 100644
index 000000000..e27180e3e
--- /dev/null
+++ b/apps/mobile/src/components/account/devices-section.ios.tsx
@@ -0,0 +1,60 @@
+import { Button, HStack, Section, Spacer, SwipeActions, Text, VStack } from '@expo/ui/swift-ui';
+import { badge, buttonStyle, disabled, foregroundStyle } from '@expo/ui/swift-ui/modifiers';
+import { FormLoadingRow } from '@mobile/components/form/loading-view.ios';
+import { FOOTNOTE, SECONDARY } from '@mobile/components/form/styles.ios';
+import { useDevicesSection } from '@mobile/runtime/use-devices-section';
+import { useTranslations } from 'use-intl';
+
+/** The account's registered devices; the view model lives in `use-devices-section`. */
+export function DevicesSection(): React.ReactNode {
+ const t = useTranslations('mobile.account');
+ const { devices, devicesError, enrolledId, busyId, refresh, confirmRevoke, describeDevice } =
+ useDevicesSection();
+
+ return (
+ // A titled Section can't also carry an action, so the header is drawn by hand.
+
+ {t('devices')}
+
+
+
+ }
+ >
+ {devicesError ? (
+ {t('devicesError')}
+ ) : devices === null ? (
+
+ ) : devices.length === 0 ? (
+ {t('devicesEmpty')}
+ ) : (
+ devices.map((device) => (
+ // Revoking is the row's swipe action, matching how saved hosts are removed.
+
+
+
+
+ {device.name}
+ {describeDevice(device)}
+
+
+ ))
+ )}
+
+ );
+}
diff --git a/apps/mobile/src/components/account/devices-section.tsx b/apps/mobile/src/components/account/devices-section.tsx
index d5c370d05..b1cb0db53 100644
--- a/apps/mobile/src/components/account/devices-section.tsx
+++ b/apps/mobile/src/components/account/devices-section.tsx
@@ -1,140 +1,60 @@
-import {
- Button,
- HStack,
- ProgressView,
- Section,
- Spacer,
- SwipeActions,
- Text,
- VStack,
-} from '@expo/ui/swift-ui';
-import { badge, buttonStyle, disabled, foregroundStyle } from '@expo/ui/swift-ui/modifiers';
-import { FOOTNOTE, SECONDARY } from '@mobile/components/form/styles';
-import { signOutOfCloud } from '@mobile/runtime/cloud/account';
-import type { CloudDevice } from '@mobile/runtime/cloud/devices';
-import { fetchDevices, getEnrolledDeviceId, revokeDevice } from '@mobile/runtime/cloud/devices';
-import { formatRelativeShort } from '@mobile/utils/relative-time';
-import { noop } from 'foxact/noop';
-import { useCallback, useEffect, useState } from 'react';
-import { Alert } from 'react-native';
+import { ListItem, Text, TextButton } from '@expo/ui/jetpack-compose';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { RowActions } from '@mobile/components/form/row-actions.android';
+import { FormHint, FormLoadingRow } from '@mobile/components/form/rows.android';
+import { FormSection } from '@mobile/components/form/section.android';
+import { useDevicesSection } from '@mobile/runtime/use-devices-section';
import { useTranslations } from 'use-intl';
-/**
- * The account's registered devices. Revoking cuts access to new tunnel tokens;
- * revoking this phone also signs it out (the cloud kills its sessions).
- */
export function DevicesSection(): React.ReactNode {
const t = useTranslations('mobile.account');
-
- const [devices, setDevices] = useState(null);
- const [devicesError, setDevicesError] = useState(false);
- const [enrolledId, setEnrolledId] = useState(null);
- const [busyId, setBusyId] = useState(null);
-
- const load = useCallback(() => {
- fetchDevices()
- .then(setDevices)
- .catch(() => setDevicesError(true));
- }, []);
-
- useEffect(() => {
- getEnrolledDeviceId().then(setEnrolledId).catch(noop);
- load();
- }, [load]);
-
- const refresh = () => {
- setDevicesError(false);
- setDevices(null);
- load();
- };
-
- const revoke = async (device: CloudDevice) => {
- setBusyId(device.id);
- try {
- await revokeDevice(device.id);
- if (device.id === enrolledId) {
- // The device revoke already removed its push token and killed this phone's sessions.
- await signOutOfCloud({ revokePushToken: false }).catch(noop);
- return;
- }
- refresh();
- } catch {
- Alert.alert(t('revokeError'));
- } finally {
- setBusyId(null);
- }
- };
-
- const confirmRevoke = (device: CloudDevice) => {
- Alert.alert(
- t('revokeTitle', { name: device.name }),
- device.id === enrolledId ? t('revokeThisDeviceMessage') : t('revokeMessage'),
- [
- { text: t('revokeCancel'), style: 'cancel' },
- {
- text: t('revoke'),
- style: 'destructive',
- onPress() {
- void revoke(device);
- },
- },
- ],
- );
- };
-
- const describeDevice = (device: CloudDevice): string => {
- const kind = t(`deviceKind.${device.kind}`);
- const platform = device.platform ? `${kind} · ${device.platform}` : kind;
- return device.lastSeenAt
- ? `${platform} · ${t('lastSeen', { time: formatRelativeShort(new Date(device.lastSeenAt).getTime()) })}`
- : platform;
- };
+ const colors = useAppMaterialColors();
+ const { devices, devicesError, enrolledId, busyId, refresh, confirmRevoke, describeDevice } =
+ useDevicesSection();
return (
- // A titled Section can't also carry an action, so the header is drawn by hand.
-
- {t('devices')}
-
-
-
+
+ {t('refresh')}
+
}
>
{devicesError ? (
- {t('devicesError')}
+ {t('devicesError')}
) : devices === null ? (
-
+
) : devices.length === 0 ? (
- {t('devicesEmpty')}
+ {t('devicesEmpty')}
) : (
devices.map((device) => (
- // Revoking is the row's swipe action, matching how saved hosts are removed. The
- // confirmation stays an RN `Alert` — that already is the native alert.
-
-
-
-
- {device.name}
- {describeDevice(device)}
-
-
+ confirmRevoke(device),
+ },
+ ]}
+ >
+
+
+ {device.name}
+
+
+
+ {device.id === enrolledId
+ ? `${describeDevice(device)} · ${t('thisDevice')}`
+ : describeDevice(device)}
+
+
+
+
))
)}
-
+
);
}
diff --git a/apps/mobile/src/components/account/profile-row.ios.tsx b/apps/mobile/src/components/account/profile-row.ios.tsx
new file mode 100644
index 000000000..93d095e17
--- /dev/null
+++ b/apps/mobile/src/components/account/profile-row.ios.tsx
@@ -0,0 +1,20 @@
+import { HStack, Image, Spacer, Text, VStack } from '@expo/ui/swift-ui';
+import { font } from '@expo/ui/swift-ui/modifiers';
+import { FOOTNOTE, SECONDARY } from '@mobile/components/form/styles.ios';
+import type { CloudUser } from '@mobile/runtime/cloud/account';
+
+/** SF Symbol rather than the account's picture: `@expo/ui`'s `Image` takes SF Symbols, asset
+ * catalog names, and local files — never a remote URL. Apple sign-in supplies no picture
+ * anyway, so this is the fallback the old avatar already showed in the common case. */
+export function ProfileRow({ user }: { user: CloudUser }): React.ReactNode {
+ return (
+
+
+
+ {user.name || user.email}
+ {user.email}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/account/profile-row.tsx b/apps/mobile/src/components/account/profile-row.tsx
index 59c45e5b4..959870707 100644
--- a/apps/mobile/src/components/account/profile-row.tsx
+++ b/apps/mobile/src/components/account/profile-row.tsx
@@ -1,20 +1,22 @@
-import { HStack, Image, Spacer, Text, VStack } from '@expo/ui/swift-ui';
-import { font } from '@expo/ui/swift-ui/modifiers';
-import { FOOTNOTE, SECONDARY } from '@mobile/components/form/styles';
+import { ListItem, Text } from '@expo/ui/jetpack-compose';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
import type { CloudUser } from '@mobile/runtime/cloud/account';
-/** SF Symbol rather than the account's picture: `@expo/ui`'s `Image` takes SF Symbols, asset
- * catalog names, and local files — never a remote URL. Apple sign-in supplies no picture
- * anyway, so this is the fallback the old avatar already showed in the common case. */
+/** Android profile row. No avatar: MD3 needs no placeholder glyph, and Apple sign-in supplies no
+ * picture anyway (the iOS variant shows an SF Symbol for the same reason). */
export function ProfileRow({ user }: { user: CloudUser }): React.ReactNode {
+ const colors = useAppMaterialColors();
+
return (
-
-
-
- {user.name || user.email}
- {user.email}
-
-
-
+
+
+ {user.name || user.email}
+
+
+
+ {user.email}
+
+
+
);
}
diff --git a/apps/mobile/src/components/connect/add-host-screen.ios.tsx b/apps/mobile/src/components/connect/add-host-screen.ios.tsx
new file mode 100644
index 000000000..4f9f6edc1
--- /dev/null
+++ b/apps/mobile/src/components/connect/add-host-screen.ios.tsx
@@ -0,0 +1,86 @@
+import { Form, Host, HStack, Section, Text, TextField, useNativeState } from '@expo/ui/swift-ui';
+import {
+ autocorrectionDisabled,
+ keyboardType,
+ onSubmit,
+ submitLabel,
+ textContentType,
+ textInputAutocapitalization,
+} from '@expo/ui/swift-ui/modifiers';
+import { useAddHost } from '@mobile/runtime/use-add-host';
+import { Stack, useRouter } from 'expo-router';
+import { useTranslations } from 'use-intl';
+
+export function AddHostScreen(): React.ReactNode {
+ const t = useTranslations('mobile.connect');
+ const router = useRouter();
+ const name = useNativeState('');
+ const url = useNativeState('');
+ const { urlInvalid, urlValid, onUrlChange, submit } = useAddHost();
+
+ const submitFields = () => submit(name.get(), url.get());
+
+ return (
+ <>
+ [
+ {
+ type: 'button',
+ label: t('cancel'),
+ accessibilityLabel: t('cancel'),
+ icon: { type: 'sfSymbol', name: 'xmark' },
+ onPress: () => router.dismiss(),
+ },
+ ],
+ unstable_headerRightItems: () => [
+ {
+ type: 'button',
+ label: t('add'),
+ accessibilityLabel: t('add'),
+ icon: { type: 'sfSymbol', name: 'checkmark' },
+ variant: 'prominent',
+ disabled: !urlValid,
+ onPress: submitFields,
+ },
+ ],
+ }}
+ />
+ {/* Form needs the viewport as its proposed size, otherwise it collapses to its content. */}
+
+
+
+ >
+ );
+}
diff --git a/apps/mobile/src/components/connect/add-host-screen.tsx b/apps/mobile/src/components/connect/add-host-screen.tsx
index 427f265c0..48d8206f4 100644
--- a/apps/mobile/src/components/connect/add-host-screen.tsx
+++ b/apps/mobile/src/components/connect/add-host-screen.tsx
@@ -1,102 +1,71 @@
-import { Form, Host, HStack, Section, Text, TextField, useNativeState } from '@expo/ui/swift-ui';
-import {
- autocorrectionDisabled,
- keyboardType,
- onSubmit,
- submitLabel,
- textContentType,
- textInputAutocapitalization,
-} from '@expo/ui/swift-ui/modifiers';
-import { HostUrlSchema, useHostRegistryStore } from '@mobile/stores/host-store';
-import { Stack, useRouter } from 'expo-router';
-import { useState } from 'react';
+import { Button, Column, OutlinedTextField, Text, useNativeState } from '@expo/ui/jetpack-compose';
+import { fillMaxWidth, padding, testID } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import { useAddHost } from '@mobile/runtime/use-add-host';
+import { Stack } from 'expo-router';
import { useTranslations } from 'use-intl';
+/** Android add-host form, reached only by deep link — the connect screen opens the in-place
+ * bottom sheet instead. Pushed with the standard toolbar (Android's formSheet drops the header);
+ * the iOS header bar items (`unstable_header*Items`) don't exist here, so submit is an in-form
+ * button and dismissal is the toolbar back arrow. */
export function AddHostScreen(): React.ReactNode {
const t = useTranslations('mobile.connect');
- const router = useRouter();
- const addHost = useHostRegistryStore((state) => state.addHost);
- const setLastActiveHostId = useHostRegistryStore((state) => state.setLastActiveHostId);
+ const colors = useAppMaterialColors();
const name = useNativeState('');
const url = useNativeState('');
- const [urlInvalid, setUrlInvalid] = useState(false);
- const [urlValid, setUrlValid] = useState(false);
+ const { urlInvalid, urlValid, onUrlChange, submit } = useAddHost();
- const submit = () => {
- const trimmedUrl = url.get().trim();
- if (!HostUrlSchema.safeParse(trimmedUrl).success) {
- setUrlInvalid(true);
- return;
- }
- const profile = addHost({ name: name.get().trim() || t('namePlaceholder'), url: trimmedUrl });
- setLastActiveHostId(profile.id);
- router.dismissTo('/threads');
- };
+ const submitFields = () => submit(name.get(), url.get());
return (
<>
- [
- {
- type: 'button',
- label: t('cancel'),
- accessibilityLabel: t('cancel'),
- icon: { type: 'sfSymbol', name: 'xmark' },
- onPress: () => router.dismiss(),
- },
- ],
- unstable_headerRightItems: () => [
- {
- type: 'button',
- label: t('add'),
- accessibilityLabel: t('add'),
- icon: { type: 'sfSymbol', name: 'checkmark' },
- variant: 'prominent',
- disabled: !urlValid,
- onPress: submit,
- },
- ],
- }}
- />
- {/* Form needs the viewport as its proposed size, otherwise it collapses to its content. */}
-
-
-
+
+
+ {t('urlPlaceholder')}
+
+
+ {urlInvalid ? t('invalidUrl') : t('emptyHint')}
+
+
+
+
+
>
);
}
diff --git a/apps/mobile/src/components/connect/add-host-sheet.android.tsx b/apps/mobile/src/components/connect/add-host-sheet.android.tsx
new file mode 100644
index 000000000..5dc9f8cbd
--- /dev/null
+++ b/apps/mobile/src/components/connect/add-host-sheet.android.tsx
@@ -0,0 +1,88 @@
+import {
+ Button,
+ Column,
+ ModalBottomSheet,
+ OutlinedTextField,
+ Text,
+ useNativeState,
+} from '@expo/ui/jetpack-compose';
+import { fillMaxWidth, imePadding, padding, testID } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import { useAddHost } from '@mobile/runtime/use-add-host';
+import { useTranslations } from 'use-intl';
+
+/** Android add-host form as an MD3 bottom sheet — the same modal family as the host and model
+ * switchers. A successful submit dismisses to the threads tab, which unmounts the sheet. */
+export function AddHostSheet({
+ isPresented,
+ onIsPresentedChange,
+}: {
+ isPresented: boolean;
+ onIsPresentedChange: (isPresented: boolean) => void;
+}): React.ReactNode {
+ const t = useTranslations('mobile.connect');
+ const colors = useAppMaterialColors();
+ const name = useNativeState('');
+ const url = useNativeState('');
+ const { urlInvalid, urlValid, onUrlChange, submit } = useAddHost();
+
+ if (!isPresented) return null;
+
+ const submitFields = () => submit(name.get(), url.get());
+
+ return (
+
+ onIsPresentedChange(false)}>
+
+
+ {t('add')}
+
+
+
+ {t('nameLabel')}
+
+
+ {t('namePlaceholder')}
+
+
+
+
+ {t('urlLabel')}
+
+
+ {t('urlPlaceholder')}
+
+
+ {urlInvalid ? t('invalidUrl') : t('emptyHint')}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/connect/connect-screen.ios.tsx b/apps/mobile/src/components/connect/connect-screen.ios.tsx
new file mode 100644
index 000000000..400930360
--- /dev/null
+++ b/apps/mobile/src/components/connect/connect-screen.ios.tsx
@@ -0,0 +1,13 @@
+import { Form, Host } from '@expo/ui/swift-ui';
+import { ConnectSections } from '@mobile/components/connect/connect-sections';
+
+export function ConnectScreen(): React.ReactNode {
+ return (
+ // Form needs the viewport as its proposed size, otherwise it collapses to its content.
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/connect/connect-screen.tsx b/apps/mobile/src/components/connect/connect-screen.tsx
new file mode 100644
index 000000000..e41320645
--- /dev/null
+++ b/apps/mobile/src/components/connect/connect-screen.tsx
@@ -0,0 +1,10 @@
+import { ConnectSections } from '@mobile/components/connect/connect-sections';
+import { FormList } from '@mobile/components/form/list.android';
+
+export function ConnectScreen(): React.ReactNode {
+ return (
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/connect/connect-sections.tsx b/apps/mobile/src/components/connect/connect-sections.tsx
new file mode 100644
index 000000000..48c8b4a94
--- /dev/null
+++ b/apps/mobile/src/components/connect/connect-sections.tsx
@@ -0,0 +1,27 @@
+import { DiscoveredHostsSection } from '@mobile/components/connect/discovered-hosts-section';
+import { MyMachinesSection } from '@mobile/components/connect/my-machines-section';
+import { SavedHostsSection } from '@mobile/components/connect/saved-hosts-section';
+import { SignInSection } from '@mobile/components/connect/sign-in-section';
+import { useCloudAccount } from '@mobile/runtime/cloud/account';
+import { useHostRegistryStore } from '@mobile/stores/host-store';
+
+/** The connect screen's section stack, single-sourced: each section resolves to its platform
+ * implementation through the Metro extension seam, so only the scaffold around this differs. */
+export function ConnectSections(): React.ReactNode {
+ const account = useCloudAccount();
+ const hosts = useHostRegistryStore((state) => state.hosts);
+
+ return (
+ <>
+ {account.status === 'signed-in' ? (
+
+ ) : account.status === 'signed-out' ? (
+
+ ) : null}
+
+ {hosts.length > 0 ? : null}
+
+
+ >
+ );
+}
diff --git a/apps/mobile/src/components/connect/discovered-hosts-section.ios.tsx b/apps/mobile/src/components/connect/discovered-hosts-section.ios.tsx
new file mode 100644
index 000000000..ea07aa40e
--- /dev/null
+++ b/apps/mobile/src/components/connect/discovered-hosts-section.ios.tsx
@@ -0,0 +1,40 @@
+import { HStack, ProgressView, Section, Text } from '@expo/ui/swift-ui';
+import { accessibilityLabel, controlSize, foregroundStyle } from '@expo/ui/swift-ui/modifiers';
+import { ManualHostRow } from '@mobile/components/connect/manual-host-row';
+import { NavigationRow } from '@mobile/components/form/navigation-row';
+import { useDiscoveredHosts } from '@mobile/runtime/use-discovered-hosts';
+import { useTranslations } from 'use-intl';
+
+export function DiscoveredHostsSection(): React.ReactNode {
+ const t = useTranslations('mobile.connect');
+ const { discoveredHosts, status, saveAndOpen } = useDiscoveredHosts();
+
+ return (
+
+ {t('discovery.title')}
+ {status === 'error' ? null : (
+
+ )}
+
+ }
+ >
+ {status === 'error' ? (
+ {t('discovery.error')}
+ ) : (
+ discoveredHosts.map((host) => (
+ saveAndOpen(host)}
+ />
+ ))
+ )}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/connect/discovered-hosts-section.tsx b/apps/mobile/src/components/connect/discovered-hosts-section.tsx
index 7d600f8b3..31721a4f2 100644
--- a/apps/mobile/src/components/connect/discovered-hosts-section.tsx
+++ b/apps/mobile/src/components/connect/discovered-hosts-section.tsx
@@ -1,46 +1,23 @@
-import { HStack, ProgressView, Section, Text } from '@expo/ui/swift-ui';
-import { accessibilityLabel, controlSize, foregroundStyle } from '@expo/ui/swift-ui/modifiers';
+import { LoadingIndicator } from '@expo/ui/jetpack-compose';
+import { size } from '@expo/ui/jetpack-compose/modifiers';
import { ManualHostRow } from '@mobile/components/connect/manual-host-row';
import { NavigationRow } from '@mobile/components/form/navigation-row';
-import { canonicalDirectHostUrl } from '@mobile/runtime/daemon-discovery';
-import { useDaemonDiscovery } from '@mobile/runtime/use-daemon-discovery';
-import { useOpenHost } from '@mobile/runtime/use-open-host';
-import { useHostRegistryStore } from '@mobile/stores/host-store';
+import { FormHint } from '@mobile/components/form/rows.android';
+import { FormSection } from '@mobile/components/form/section.android';
+import { useDiscoveredHosts } from '@mobile/runtime/use-discovered-hosts';
import { useTranslations } from 'use-intl';
export function DiscoveredHostsSection(): React.ReactNode {
const t = useTranslations('mobile.connect');
- const { hosts, status } = useDaemonDiscovery();
- const openHost = useOpenHost();
- const savedHosts = useHostRegistryStore((state) => state.hosts);
- const addHost = useHostRegistryStore((state) => state.addHost);
- const savedUrls = new Set();
- for (let i = 0, len = savedHosts.length; i < len; i++) {
- const host = savedHosts[i];
- if ('url' in host) savedUrls.add(canonicalDirectHostUrl(host.url));
- }
- const discoveredHosts = hosts.filter((host) => !savedUrls.has(canonicalDirectHostUrl(host.url)));
-
- const saveAndOpen = (host: (typeof discoveredHosts)[number]) => {
- const profile = addHost({ name: host.name, url: host.url });
- openHost(profile.id);
- };
+ const { discoveredHosts, status, saveAndOpen } = useDiscoveredHosts();
return (
-
- {t('discovery.title')}
- {status === 'error' ? null : (
-
- )}
-
- }
+ }
>
{status === 'error' ? (
- {t('discovery.error')}
+ {t('discovery.error')}
) : (
discoveredHosts.map((host) => (
-
+
);
}
diff --git a/apps/mobile/src/components/connect/manual-host-row.ios.tsx b/apps/mobile/src/components/connect/manual-host-row.ios.tsx
new file mode 100644
index 000000000..af814f644
--- /dev/null
+++ b/apps/mobile/src/components/connect/manual-host-row.ios.tsx
@@ -0,0 +1,10 @@
+import { NavigationRow } from '@mobile/components/form/navigation-row';
+import { useRouter } from 'expo-router';
+import { useTranslations } from 'use-intl';
+
+export function ManualHostRow(): React.ReactNode {
+ const t = useTranslations('mobile.connect');
+ const router = useRouter();
+
+ return router.push('/add-host')} />;
+}
diff --git a/apps/mobile/src/components/connect/manual-host-row.tsx b/apps/mobile/src/components/connect/manual-host-row.tsx
index af814f644..e19bf92eb 100644
--- a/apps/mobile/src/components/connect/manual-host-row.tsx
+++ b/apps/mobile/src/components/connect/manual-host-row.tsx
@@ -1,10 +1,18 @@
+import { AddHostSheet } from '@mobile/components/connect/add-host-sheet.android';
import { NavigationRow } from '@mobile/components/form/navigation-row';
-import { useRouter } from 'expo-router';
+import { useState } from 'react';
import { useTranslations } from 'use-intl';
+/** Android opens the add-host form as a bottom sheet in place — the same modal family as the
+ * host and model switchers — instead of pushing the form-sheet route iOS uses. */
export function ManualHostRow(): React.ReactNode {
const t = useTranslations('mobile.connect');
- const router = useRouter();
+ const [open, setOpen] = useState(false);
- return router.push('/add-host')} />;
+ return (
+ <>
+ setOpen(true)} />
+
+ >
+ );
}
diff --git a/apps/mobile/src/components/connect/my-machines-section.ios.tsx b/apps/mobile/src/components/connect/my-machines-section.ios.tsx
new file mode 100644
index 000000000..a0d6a675c
--- /dev/null
+++ b/apps/mobile/src/components/connect/my-machines-section.ios.tsx
@@ -0,0 +1,48 @@
+import { Button, HStack, Section, Spacer, Text } from '@expo/ui/swift-ui';
+import { buttonStyle, foregroundStyle } from '@expo/ui/swift-ui/modifiers';
+import { FormLoadingRow } from '@mobile/components/form/loading-view.ios';
+import { NavigationRow } from '@mobile/components/form/navigation-row';
+import { FOOTNOTE, SECONDARY } from '@mobile/components/form/styles.ios';
+import { useMyMachines } from '@mobile/runtime/use-my-machines';
+import { useTranslations } from 'use-intl';
+
+/** Online machines; the view model lives in `use-my-machines`. */
+export function MyMachinesSection({ userId }: { userId: string }): React.ReactNode {
+ const t = useTranslations('mobile.connect.cloud');
+ const { onlineHosts, hostsError, refresh, saveAndOpen } = useMyMachines(userId);
+
+ return (
+ // A titled Section can't also carry an action, so the header is drawn by hand —
+ // footnote + secondary is what SwiftUI gives a plain `title` on iOS.
+
+ {t('machines')}
+
+
+
+ }
+ >
+ {hostsError ? (
+ {t('error')}
+ ) : onlineHosts === null ? (
+
+ ) : onlineHosts.length === 0 ? (
+ {t('empty')}
+ ) : (
+ onlineHosts.map((host) => (
+ saveAndOpen(host)}
+ />
+ ))
+ )}
+
+ );
+}
diff --git a/apps/mobile/src/components/connect/my-machines-section.tsx b/apps/mobile/src/components/connect/my-machines-section.tsx
index df8ee5b19..2ec610245 100644
--- a/apps/mobile/src/components/connect/my-machines-section.tsx
+++ b/apps/mobile/src/components/connect/my-machines-section.tsx
@@ -1,74 +1,30 @@
-import { Button, HStack, ProgressView, Section, Spacer, Text } from '@expo/ui/swift-ui';
-import { buttonStyle, foregroundStyle } from '@expo/ui/swift-ui/modifiers';
+import { Text, TextButton } from '@expo/ui/jetpack-compose';
import { NavigationRow } from '@mobile/components/form/navigation-row';
-import { FOOTNOTE, SECONDARY } from '@mobile/components/form/styles';
-import { ensureDeviceRegistered } from '@mobile/runtime/cloud/devices';
-import type { OnlineHost } from '@mobile/runtime/cloud/hosts';
-import { fetchOnlineHosts } from '@mobile/runtime/cloud/hosts';
-import { useOpenHost } from '@mobile/runtime/use-open-host';
-import { useHostRegistryStore } from '@mobile/stores/host-store';
-import { noop } from 'foxact/noop';
-import { useCallback, useEffect, useState } from 'react';
+import { FormHint, FormLoadingRow } from '@mobile/components/form/rows.android';
+import { FormSection } from '@mobile/components/form/section.android';
+import { useMyMachines } from '@mobile/runtime/use-my-machines';
import { useTranslations } from 'use-intl';
-/** Online machines (daemons connected to the relay) — tapping one saves it as a tunnel host and opens it. */
+/** Android online machines; the view model lives in `use-my-machines`. */
export function MyMachinesSection({ userId }: { userId: string }): React.ReactNode {
const t = useTranslations('mobile.connect.cloud');
- const openHost = useOpenHost();
- const addTunnelHost = useHostRegistryStore((state) => state.addTunnelHost);
-
- const [onlineHosts, setOnlineHosts] = useState(null);
- const [hostsError, setHostsError] = useState(false);
-
- const load = useCallback(() => {
- fetchOnlineHosts()
- .then(setOnlineHosts)
- .catch(() => setHostsError(true));
- }, []);
-
- const refresh = () => {
- setHostsError(false);
- setOnlineHosts(null);
- load();
- };
-
- useEffect(() => {
- // Best-effort: registration only lists the phone under the account's
- // devices; discovering and connecting to hosts does not depend on it.
- ensureDeviceRegistered(userId).catch(noop);
- load();
- }, [userId, load]);
-
- const saveAndOpen = (host: OnlineHost) => {
- const profile = addTunnelHost({
- name: host.name ?? host.hostId.slice(0, 8),
- tunnelHostId: host.hostId,
- });
- openHost(profile.id);
- };
+ const { onlineHosts, hostsError, refresh, saveAndOpen } = useMyMachines(userId);
return (
- // A titled Section can't also carry an action, so the header is drawn by hand —
- // footnote + secondary is what SwiftUI gives a plain `title` on iOS.
-
- {t('machines')}
-
-
-
+
+ {t('refresh')}
+
}
>
{hostsError ? (
- {t('error')}
+ {t('error')}
) : onlineHosts === null ? (
-
+
) : onlineHosts.length === 0 ? (
- {t('empty')}
+ {t('empty')}
) : (
onlineHosts.map((host) => (
))
)}
-
+
);
}
diff --git a/apps/mobile/src/components/connect/saved-hosts-section.ios.tsx b/apps/mobile/src/components/connect/saved-hosts-section.ios.tsx
new file mode 100644
index 000000000..727baac44
--- /dev/null
+++ b/apps/mobile/src/components/connect/saved-hosts-section.ios.tsx
@@ -0,0 +1,31 @@
+import { Button, Section, SwipeActions } from '@expo/ui/swift-ui';
+import { NavigationRow } from '@mobile/components/form/navigation-row';
+import { useOpenHost } from '@mobile/runtime/use-open-host';
+import { useHostRegistryStore } from '@mobile/stores/host-store';
+import { useTranslations } from 'use-intl';
+
+/** Saved hosts, direct or tunnelled. Removal is the list's own swipe action rather than a
+ * row button, so the row itself stays a single tap target for opening the host. */
+export function SavedHostsSection(): React.ReactNode {
+ const t = useTranslations('mobile.connect');
+ const openHost = useOpenHost();
+ const hosts = useHostRegistryStore((state) => state.hosts);
+ const removeHost = useHostRegistryStore((state) => state.removeHost);
+
+ return (
+
+ {hosts.map((host) => (
+
+
+
+ openHost(host.id)}
+ />
+
+ ))}
+
+ );
+}
diff --git a/apps/mobile/src/components/connect/saved-hosts-section.tsx b/apps/mobile/src/components/connect/saved-hosts-section.tsx
index 727baac44..c55bb9369 100644
--- a/apps/mobile/src/components/connect/saved-hosts-section.tsx
+++ b/apps/mobile/src/components/connect/saved-hosts-section.tsx
@@ -1,31 +1,38 @@
-import { Button, Section, SwipeActions } from '@expo/ui/swift-ui';
-import { NavigationRow } from '@mobile/components/form/navigation-row';
+import { ListItem, Text } from '@expo/ui/jetpack-compose';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { RowActions } from '@mobile/components/form/row-actions.android';
+import { FormSection } from '@mobile/components/form/section.android';
import { useOpenHost } from '@mobile/runtime/use-open-host';
import { useHostRegistryStore } from '@mobile/stores/host-store';
import { useTranslations } from 'use-intl';
-/** Saved hosts, direct or tunnelled. Removal is the list's own swipe action rather than a
- * row button, so the row itself stays a single tap target for opening the host. */
export function SavedHostsSection(): React.ReactNode {
const t = useTranslations('mobile.connect');
+ const colors = useAppMaterialColors();
const openHost = useOpenHost();
const hosts = useHostRegistryStore((state) => state.hosts);
const removeHost = useHostRegistryStore((state) => state.removeHost);
return (
-
+
{hosts.map((host) => (
-
-
-
- openHost(host.id)}
- />
-
+ openHost(host.id)}
+ actions={[{ label: t('remove'), destructive: true, onPress: () => removeHost(host.id) }]}
+ >
+
+
+ {host.name}
+
+
+
+ {'url' in host ? host.url : t('viaTunnel')}
+
+
+
+
))}
-
+
);
}
diff --git a/apps/mobile/src/components/connect/sign-in-section.ios.tsx b/apps/mobile/src/components/connect/sign-in-section.ios.tsx
new file mode 100644
index 000000000..daea8c1fc
--- /dev/null
+++ b/apps/mobile/src/components/connect/sign-in-section.ios.tsx
@@ -0,0 +1,14 @@
+import { Button, Section, Text } from '@expo/ui/swift-ui';
+import { useRouter } from 'expo-router';
+import { useTranslations } from 'use-intl';
+
+/** Signed-out lead-in: the account is how machines appear on the connect screen. */
+export function SignInSection(): React.ReactNode {
+ const t = useTranslations('mobile.connect.cloud');
+ const router = useRouter();
+ return (
+ {t('hint')}}>
+
+ );
+}
diff --git a/apps/mobile/src/components/connect/sign-in-section.tsx b/apps/mobile/src/components/connect/sign-in-section.tsx
index daea8c1fc..22654e041 100644
--- a/apps/mobile/src/components/connect/sign-in-section.tsx
+++ b/apps/mobile/src/components/connect/sign-in-section.tsx
@@ -1,4 +1,6 @@
-import { Button, Section, Text } from '@expo/ui/swift-ui';
+import { Button, Text } from '@expo/ui/jetpack-compose';
+import { fillMaxWidth, padding } from '@expo/ui/jetpack-compose/modifiers';
+import { FormSection } from '@mobile/components/form/section.android';
import { useRouter } from 'expo-router';
import { useTranslations } from 'use-intl';
@@ -6,9 +8,15 @@ import { useTranslations } from 'use-intl';
export function SignInSection(): React.ReactNode {
const t = useTranslations('mobile.connect.cloud');
const router = useRouter();
+
return (
- {t('hint')}}>
-
+
+
+
);
}
diff --git a/apps/mobile/src/components/conversation/activity-row.ios.tsx b/apps/mobile/src/components/conversation/activity-row.ios.tsx
new file mode 100644
index 000000000..eb841844c
--- /dev/null
+++ b/apps/mobile/src/components/conversation/activity-row.ios.tsx
@@ -0,0 +1,70 @@
+import {
+ Button,
+ DisclosureGroup,
+ Host,
+ HStack,
+ Image,
+ ProgressView,
+ Spacer,
+ Text,
+} from '@expo/ui/swift-ui';
+import {
+ buttonStyle,
+ disabled,
+ font,
+ foregroundStyle,
+ frame,
+ lineLimit,
+ textSelection,
+} from '@expo/ui/swift-ui/modifiers';
+import { useState } from 'react';
+import { useTranslations } from 'use-intl';
+import type { ReasoningRowProps, ToolRowProps } from './activity-row.types';
+
+const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
+const BODY = font({ textStyle: 'subheadline' });
+
+export function ToolRow({ title, status, onPress }: ToolRowProps): React.ReactNode {
+ const t = useTranslations('mobile.chat');
+ return (
+
+
+
+ );
+}
+
+export function ReasoningRow({ text, streaming }: ReasoningRowProps): React.ReactNode {
+ const [expanded, setExpanded] = useState(false);
+ const t = useTranslations('mobile.conversation');
+ return (
+
+
+
+
+ {t('reasoning')}
+ {streaming ? : null}
+
+
+ {text}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/activity-row.tsx b/apps/mobile/src/components/conversation/activity-row.tsx
new file mode 100644
index 000000000..ea154e914
--- /dev/null
+++ b/apps/mobile/src/components/conversation/activity-row.tsx
@@ -0,0 +1,93 @@
+import { CircularProgressIndicator, Column, Icon, Row, Text } from '@expo/ui/jetpack-compose';
+import {
+ clickable,
+ defaultMinSize,
+ fillMaxWidth,
+ size,
+ weight,
+} from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import { useState } from 'react';
+import { useTranslations } from 'use-intl';
+import chevronRight from '../../../assets/icons/chevron-right.xml';
+import expandMore from '../../../assets/icons/expand-more.xml';
+import type { ReasoningRowProps, ToolRowProps } from './activity-row.types';
+
+export function ToolRow({ title, status, onPress }: ToolRowProps): React.ReactNode {
+ const colors = useAppMaterialColors();
+ const t = useTranslations('mobile.chat');
+ return (
+
+
+
+ {title}
+
+ {status === 'failed' ? (
+
+ {t('failed')}
+
+ ) : null}
+ {status === 'in_progress' ? (
+
+ ) : onPress ? (
+
+ ) : null}
+
+
+ );
+}
+
+export function ReasoningRow({ text, streaming }: ReasoningRowProps): React.ReactNode {
+ const [expanded, setExpanded] = useState(false);
+ const colors = useAppMaterialColors();
+ const t = useTranslations('mobile.conversation');
+ return (
+
+
+ setExpanded((current) => !current)),
+ ]}
+ >
+
+ {t('reasoning')}
+
+ {streaming ? : null}
+
+
+ {expanded ? (
+
+ {text}
+
+ ) : null}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/activity-row.types.ts b/apps/mobile/src/components/conversation/activity-row.types.ts
new file mode 100644
index 000000000..0c04ec25f
--- /dev/null
+++ b/apps/mobile/src/components/conversation/activity-row.types.ts
@@ -0,0 +1,12 @@
+import type { ToolCall } from '@linkcode/schema';
+
+export interface ToolRowProps {
+ title: string;
+ status: ToolCall['status'];
+ onPress?: () => void;
+}
+
+export interface ReasoningRowProps {
+ text: string;
+ streaming: boolean;
+}
diff --git a/apps/mobile/src/components/conversation/composer-backdrop.ios.tsx b/apps/mobile/src/components/conversation/composer-backdrop.ios.tsx
new file mode 100644
index 000000000..ae3ba750c
--- /dev/null
+++ b/apps/mobile/src/components/conversation/composer-backdrop.ios.tsx
@@ -0,0 +1,26 @@
+import { Host, Spacer, VStack } from '@expo/ui/swift-ui';
+import { frame, glassEffect } from '@expo/ui/swift-ui/modifiers';
+import { USES_IOS_26_NAVIGATION } from '@mobile/components/shell/ios-26-navigation';
+import { View } from 'react-native';
+
+const GLASS = [
+ frame({ maxWidth: Number.POSITIVE_INFINITY, maxHeight: Number.POSITIVE_INFINITY }),
+ glassEffect({
+ glass: { variant: 'regular' },
+ shape: 'roundedRectangle',
+ cornerRadius: 24,
+ }),
+];
+
+export function ComposerBackdrop(): React.ReactNode {
+ if (!USES_IOS_26_NAVIGATION) return null;
+ return (
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/composer-backdrop.ts b/apps/mobile/src/components/conversation/composer-backdrop.ts
new file mode 100644
index 000000000..8c53975f0
--- /dev/null
+++ b/apps/mobile/src/components/conversation/composer-backdrop.ts
@@ -0,0 +1,3 @@
+export function ComposerBackdrop(): React.ReactNode {
+ return null;
+}
diff --git a/apps/mobile/src/components/conversation/composer.tsx b/apps/mobile/src/components/conversation/composer.tsx
index f1fed929f..0087de1c2 100644
--- a/apps/mobile/src/components/conversation/composer.tsx
+++ b/apps/mobile/src/components/conversation/composer.tsx
@@ -1,30 +1,17 @@
-import { Host, Spacer, VStack } from '@expo/ui/swift-ui';
-import { frame, glassEffect } from '@expo/ui/swift-ui/modifiers';
import { SendButton } from '@mobile/components/conversation/send-button';
import { USES_IOS_26_NAVIGATION } from '@mobile/components/shell/ios-26-navigation';
-import { useThemeColor } from 'heroui-native';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import { Text, TextInput, View } from 'react-native';
import { useTranslations } from 'use-intl';
+import { ComposerBackdrop } from './composer-backdrop';
/** Cap the growing input so a long draft scrolls internally instead of eating the screen. */
const MAX_INPUT_HEIGHT = 140;
-/** Matches the card's `rounded-3xl`, so the glass shape and the RN clip agree. */
-const CARD_RADIUS = 24;
-
-const GLASS = [
- frame({ maxWidth: Number.POSITIVE_INFINITY, maxHeight: Number.POSITIVE_INFINITY }),
- glassEffect({
- glass: { variant: 'regular' },
- shape: 'roundedRectangle',
- cornerRadius: CARD_RADIUS,
- }),
-];
-
/** The message composer card, shaped like the web composer: the editor on top and a footer row
* below it — optional tool slots left and trailing, then the circular send/stop action. Chat
* screens pass no tools; the new-thread draft fills the slots with its start-option chips.
- * Send and stop are wired by the screen; this owns nothing but the draft text. */
+ * Draft state and send/stop behavior belong to the screen's runtime hook. */
export function Composer({
onSend,
onStop,
@@ -52,7 +39,7 @@ export function Composer({
trailing?: React.ReactNode;
}): React.ReactNode {
const t = useTranslations('mobile.conversation');
- const muted = useThemeColor('muted');
+ const palette = useNativePalette();
const trimmed = text.trim();
const canSend = !disabled && !sendBlocked && trimmed.length > 0;
@@ -64,32 +51,33 @@ export function Composer({
};
return (
-
- {error ? {error} : null}
+
+ {error ? (
+
+ {error}
+
+ ) : null}
{/* On iOS 26 the card's material is real Liquid Glass, drawn by a SwiftUI backdrop behind
the RN input (the input itself must stay RN: multiline auto-grow + keyboard riding).
Pre-26 and Android keep the filled card. */}
- {USES_IOS_26_NAVIGATION ? (
-
-
-
-
-
-
-
- ) : null}
+
{sf ? : null}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/__tests__/use-question-response.test.ts b/apps/mobile/src/components/conversation/prompt-dock/__tests__/use-question-response.test.ts
new file mode 100644
index 000000000..374f40db2
--- /dev/null
+++ b/apps/mobile/src/components/conversation/prompt-dock/__tests__/use-question-response.test.ts
@@ -0,0 +1,85 @@
+// @vitest-environment jsdom
+import type { Question } from '@linkcode/schema';
+import { act, renderHook } from '@testing-library/react';
+import { expect, it, vi } from 'vitest';
+import { useQuestionResponse } from '../use-question-response';
+
+const questions: Question[] = [
+ {
+ questionId: 'scope',
+ prompt: 'Scope?',
+ multiSelect: false,
+ options: [
+ { optionId: 'small', label: 'Small' },
+ { optionId: 'large', label: 'Large' },
+ ],
+ },
+ {
+ questionId: 'checks',
+ prompt: 'Checks?',
+ multiSelect: true,
+ options: [
+ { optionId: 'unit', label: 'Unit' },
+ { optionId: 'native', label: 'Native' },
+ ],
+ },
+];
+
+it('preserves earlier answers when navigating back and submits the entire batch', () => {
+ const respond = vi.fn();
+ const { result } = renderHook(() => useQuestionResponse(questions, false, respond));
+ act(() => result.current.selectOption('small'));
+ expect(result.current.current).toBe(2);
+ act(() => result.current.selectOption('unit'));
+ act(() => result.current.selectOption('native'));
+ act(() => result.current.previous(result.current.draft));
+ expect(result.current.draft.selected).toEqual(['small']);
+ act(() => result.current.selectOption('large'));
+ expect(result.current.draft.selected).toEqual(['unit', 'native']);
+ act(() => result.current.selectOption('unit'));
+ act(() => result.current.advance(result.current.draft));
+ expect(respond).toHaveBeenCalledExactlyOnceWith({
+ outcome: 'answered',
+ answers: [
+ { questionId: 'scope', selectedOptionIds: ['large'] },
+ { questionId: 'checks', selectedOptionIds: ['native'] },
+ ],
+ });
+});
+
+it('preserves native text across back navigation and gives custom text precedence over choices', () => {
+ const respond = vi.fn();
+ const { result } = renderHook(() => useQuestionResponse(questions, false, respond));
+ act(() => result.current.selectOption('small'));
+ act(() => result.current.previous({ selected: ['unit'], customText: ' Smoke tests ' }));
+ act(() => result.current.selectOption('small'));
+ expect(result.current.draft.customText).toBe(' Smoke tests ');
+ act(() => result.current.advance(result.current.draft));
+ expect(respond.mock.calls[0][0].answers[1]).toEqual({
+ questionId: 'checks',
+ selectedOptionIds: [],
+ customText: 'Smoke tests',
+ });
+});
+
+it('locks edits and navigation while responding and allows retry afterward', () => {
+ const respond = vi.fn();
+ const { result, rerender } = renderHook(
+ ({ responding }) => useQuestionResponse(questions, responding, respond),
+ { initialProps: { responding: false } },
+ );
+ act(() => result.current.selectOption('small'));
+ rerender({ responding: true });
+ act(() => {
+ result.current.selectOption('unit');
+ result.current.setDraft({ selected: [], customText: 'late edit' });
+ result.current.previous(result.current.draft);
+ result.current.advance(result.current.draft);
+ });
+ expect(result.current.current).toBe(2);
+ expect(result.current.draft).toEqual({ selected: [], customText: '' });
+ expect(respond).not.toHaveBeenCalled();
+ rerender({ responding: false });
+ act(() => result.current.advance(result.current.draft));
+ expect(respond).toHaveBeenCalledOnce();
+});
diff --git a/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.ios.tsx b/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.ios.tsx
new file mode 100644
index 000000000..f9207e679
--- /dev/null
+++ b/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.ios.tsx
@@ -0,0 +1,86 @@
+import { Button, Host, HStack, Image, Spacer, Text, VStack } from '@expo/ui/swift-ui';
+import {
+ buttonStyle,
+ contentShape,
+ disabled,
+ font,
+ foregroundStyle,
+ lineLimit,
+ onTapGesture,
+ shapes,
+} from '@expo/ui/swift-ui/modifiers';
+import type { PermissionPromptProps } from '@mobile/components/conversation/prompt-dock/permission-prompt.shared';
+import {
+ DANGER_KINDS,
+ detailRows,
+} from '@mobile/components/conversation/prompt-dock/permission-prompt.shared';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { View } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
+const TERTIARY = foregroundStyle({ type: 'hierarchical', style: 'tertiary' });
+const MONO_FOOTNOTE = font({ textStyle: 'footnote', design: 'monospaced' });
+const WHOLE_ROW = contentShape(shapes.rectangle());
+
+/**
+ * Desktop `PermissionPrompt` grammar on SwiftUI: title + skip, mono detail rows, one tappable
+ * row per option (deny options draw destructive red). The RN shell provides the card chrome so
+ * it themes with the rest of the conversation surface.
+ */
+export function PermissionPrompt({
+ toolCall,
+ options,
+ responding,
+ onRespond,
+}: PermissionPromptProps): React.ReactNode {
+ const t = useTranslations('mobile.chat');
+ const palette = useNativePalette();
+
+ return (
+
+
+
+
+
+ {t('allowTitle', { title: toolCall.title ?? '' })}
+
+
+ {
+ if (!responding) onRespond({ outcome: 'cancelled' });
+ }),
+ ]}
+ />
+
+ {detailRows(toolCall).map((row) => (
+
+ {row.value}
+
+ ))}
+
+ {options.map((option) => (
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.shared.ts b/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.shared.ts
new file mode 100644
index 000000000..f342bc1c8
--- /dev/null
+++ b/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.shared.ts
@@ -0,0 +1,52 @@
+import type { PermissionOption, PermissionOutcome, ToolCallUpdate } from '@linkcode/schema';
+
+export interface PermissionPromptProps {
+ toolCall: ToolCallUpdate;
+ options: PermissionOption[];
+ responding: boolean;
+ onRespond: (outcome: PermissionOutcome) => void;
+}
+
+export const DANGER_KINDS = new Set(['reject_once', 'reject_always']);
+
+export interface DetailRow {
+ key: string;
+ value: string;
+}
+
+/** The most identifying facts of the pending call: touched paths, then command/url inputs.
+ * Raw input JSON is deliberately not dumped — an unrecognized tool still shows its scalar
+ * fields through `locations`/`content`, and the model keeps the rest. */
+export function detailRows(toolCall: ToolCallUpdate): DetailRow[] {
+ const rows: DetailRow[] = [];
+ if (toolCall.locations != null) {
+ for (let i = 0, len = toolCall.locations.length; i < len; i++) {
+ const location = toolCall.locations[i];
+ rows.push({ key: `loc:${location.path}`, value: location.path });
+ }
+ }
+ if (toolCall.content != null) {
+ for (let i = 0, len = toolCall.content.length; i < len; i++) {
+ const content = toolCall.content[i];
+ if (content.type === 'diff' && !rows.some((row) => row.value === content.path)) {
+ rows.push({ key: `diff:${content.path}`, value: content.path });
+ }
+ }
+ }
+ const input = toolCall.rawInput;
+ if (typeof input === 'object' && input !== null && !Array.isArray(input)) {
+ const record = input as Record;
+ const pathKeys = ['file_path', 'path', 'notebook_path', 'filePath'];
+ for (let i = 0, len = pathKeys.length; i < len; i++) {
+ const key = pathKeys[i];
+ const value = record[key];
+ if (typeof value === 'string' && !rows.some((row) => row.value === value)) {
+ rows.push({ key: `path:${key}`, value });
+ break;
+ }
+ }
+ if (typeof record.command === 'string') rows.push({ key: 'command', value: record.command });
+ if (typeof record.url === 'string') rows.push({ key: 'url', value: record.url });
+ }
+ return rows;
+}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.tsx b/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.tsx
index 04419e0c6..f8acd809a 100644
--- a/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.tsx
+++ b/apps/mobile/src/components/conversation/prompt-dock/permission-prompt.tsx
@@ -1,126 +1,75 @@
-import { Button, Host, HStack, Image, Spacer, Text, VStack } from '@expo/ui/swift-ui';
+import { FilledTonalButton, FlowRow, Text, TextButton } from '@expo/ui/jetpack-compose';
+import { fillMaxWidth } from '@expo/ui/jetpack-compose/modifiers';
+import type { PermissionPromptProps } from '@mobile/components/conversation/prompt-dock/permission-prompt.shared';
import {
- buttonStyle,
- contentShape,
- disabled,
- font,
- foregroundStyle,
- lineLimit,
- onTapGesture,
- shapes,
-} from '@expo/ui/swift-ui/modifiers';
-import type { PermissionOption, PermissionOutcome, ToolCallUpdate } from '@linkcode/schema';
-import { View } from 'react-native';
+ DANGER_KINDS,
+ detailRows,
+} from '@mobile/components/conversation/prompt-dock/permission-prompt.shared';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { NativeIconButton } from '@mobile/components/form/icon-button';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import { Text as RNText, ScrollView, View } from 'react-native';
import { useTranslations } from 'use-intl';
-const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
-const TERTIARY = foregroundStyle({ type: 'hierarchical', style: 'tertiary' });
-const MONO_FOOTNOTE = font({ textStyle: 'footnote', design: 'monospaced' });
-const WHOLE_ROW = contentShape(shapes.rectangle());
-
-const DANGER_KINDS = new Set(['reject_once', 'reject_always']);
-
-interface DetailRow {
- key: string;
- value: string;
-}
-
-/** The most identifying facts of the pending call: touched paths, then command/url inputs.
- * Raw input JSON is deliberately not dumped — an unrecognized tool still shows its scalar
- * fields through `locations`/`content`, and the model keeps the rest. */
-function detailRows(toolCall: ToolCallUpdate): DetailRow[] {
- const rows: DetailRow[] = [];
- if (toolCall.locations != null) {
- for (let i = 0, len = toolCall.locations.length; i < len; i++) {
- const location = toolCall.locations[i];
- rows.push({ key: `loc:${location.path}`, value: location.path });
- }
- }
- if (toolCall.content != null) {
- for (let i = 0, len = toolCall.content.length; i < len; i++) {
- const content = toolCall.content[i];
- if (content.type === 'diff' && !rows.some((row) => row.value === content.path)) {
- rows.push({ key: `diff:${content.path}`, value: content.path });
- }
- }
- }
- const input = toolCall.rawInput;
- if (typeof input === 'object' && input !== null && !Array.isArray(input)) {
- const record = input as Record;
- const pathKeys = ['file_path', 'path', 'notebook_path', 'filePath'];
- for (let i = 0, len = pathKeys.length; i < len; i++) {
- const key = pathKeys[i];
- const value = record[key];
- if (typeof value === 'string' && !rows.some((row) => row.value === value)) {
- rows.push({ key: `path:${key}`, value });
- break;
- }
- }
- if (typeof record.command === 'string') rows.push({ key: 'command', value: record.command });
- if (typeof record.url === 'string') rows.push({ key: 'url', value: record.url });
- }
- return rows;
-}
-
-/**
- * Desktop `PermissionPrompt` grammar on SwiftUI: title + skip, mono detail rows, one tappable
- * row per option (deny options draw destructive red). The RN shell provides the card chrome so
- * it themes with the rest of the conversation surface.
- */
export function PermissionPrompt({
toolCall,
options,
responding,
onRespond,
-}: {
- toolCall: ToolCallUpdate;
- options: PermissionOption[];
- responding: boolean;
- onRespond: (outcome: PermissionOutcome) => void;
-}): React.ReactNode {
+}: PermissionPromptProps): React.ReactNode {
const t = useTranslations('mobile.chat');
+ const colors = useAppMaterialColors();
return (
-
-
-
-
-
- {t('allowTitle', { title: toolCall.title ?? '' })}
-
-
- {
- if (!responding) onRespond({ outcome: 'cancelled' });
- }),
- ]}
- />
-
- {detailRows(toolCall).map((row) => (
-
- {row.value}
-
- ))}
-
- {options.map((option) => (
-
-
-
+ enabled={!responding}
+ colors={DANGER_KINDS.has(option.kind) ? { contentColor: colors.error } : undefined}
+ onClick={() => onRespond({ outcome: 'selected', optionId: option.optionId })}
+ contentPadding={{ start: 12, end: 12 }}
+ >
+ {option.name}
+
+ );
+ })}
+
+
);
}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/plan-tracker.ios.tsx b/apps/mobile/src/components/conversation/prompt-dock/plan-tracker.ios.tsx
new file mode 100644
index 000000000..c1865df55
--- /dev/null
+++ b/apps/mobile/src/components/conversation/prompt-dock/plan-tracker.ios.tsx
@@ -0,0 +1,102 @@
+import { DisclosureGroup, Gauge, Host, HStack, Image, Text, VStack } from '@expo/ui/swift-ui';
+import {
+ font,
+ foregroundStyle,
+ frame,
+ gaugeStyle,
+ lineLimit,
+ scaleEffect,
+ strikethrough,
+} from '@expo/ui/swift-ui/modifiers';
+import type { CurrentPlan } from '@linkcode/ui/native';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { useState } from 'react';
+import { View } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
+const STRIKE = strikethrough({ isActive: true, pattern: 'solid' });
+
+const STATUS_ICON = {
+ pending: 'circle',
+ in_progress: 'circle.lefthalf.filled',
+ completed: 'checkmark.circle.fill',
+ cancelled: 'xmark.circle',
+} as const;
+
+/**
+ * Desktop `StepPromptRow` equivalent: a collapsed `Step N/M · current entry` row with a
+ * progress gauge, disclosing the full entry list in place.
+ */
+export function PlanTracker({ plan }: { plan: CurrentPlan }): React.ReactNode {
+ const t = useTranslations('mobile.chat');
+ const palette = useNativePalette();
+ const [expanded, setExpanded] = useState(false);
+
+ const entries = plan.item.plan.entries;
+ const current = entries[plan.currentIndex];
+
+ return (
+
+
+
+
+
+
+
+ {t('stepLabel', {
+ current: plan.currentIndex + 1,
+ total: plan.total,
+ })}
+
+
+ {current.content}
+
+
+
+
+ {entries.map((entry, index) => (
+
+
+
+ {entry.content}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/plan-tracker.tsx b/apps/mobile/src/components/conversation/prompt-dock/plan-tracker.tsx
index 4f309d422..08aef1daf 100644
--- a/apps/mobile/src/components/conversation/prompt-dock/plan-tracker.tsx
+++ b/apps/mobile/src/components/conversation/prompt-dock/plan-tracker.tsx
@@ -1,92 +1,108 @@
-import { DisclosureGroup, Gauge, Host, HStack, Image, Text, VStack } from '@expo/ui/swift-ui';
+import { CircularProgressIndicator, Column, Row, Text } from '@expo/ui/jetpack-compose';
import {
- font,
- foregroundStyle,
- frame,
- gaugeStyle,
- lineLimit,
- strikethrough,
-} from '@expo/ui/swift-ui/modifiers';
+ clickable,
+ defaultMinSize,
+ fillMaxWidth,
+ size,
+ weight,
+} from '@expo/ui/jetpack-compose/modifiers';
import type { CurrentPlan } from '@linkcode/ui/native';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
import { useState } from 'react';
import { View } from 'react-native';
import { useTranslations } from 'use-intl';
-const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
-const STRIKE = strikethrough({ isActive: true, pattern: 'solid' });
-
-const STATUS_ICON = {
- pending: 'circle',
- in_progress: 'circle.lefthalf.filled',
- completed: 'checkmark.circle.fill',
- cancelled: 'xmark.circle',
+/** Text stand-ins for the SF status glyphs — no icon assets on the Compose side. */
+const STATUS_GLYPH = {
+ pending: '○',
+ in_progress: '◐',
+ completed: '●',
+ cancelled: '✕',
} as const;
/**
- * Desktop `StepPromptRow` equivalent: a collapsed `Step N/M · current entry` row with a
- * progress gauge, disclosing the full entry list in place.
+ * Android plan tracker, mirroring the SwiftUI DisclosureGroup: a collapsed `Step N/M · current
+ * entry` row with a circular progress ring, expanding the full entry list in place on tap.
*/
export function PlanTracker({ plan }: { plan: CurrentPlan }): React.ReactNode {
const t = useTranslations('mobile.chat');
+ const colors = useAppMaterialColors();
const [expanded, setExpanded] = useState(false);
const entries = plan.item.plan.entries;
const current = entries[plan.currentIndex];
return (
-
-
-
-
-
-
-
- {t('stepLabel', {
- current: plan.currentIndex + 1,
- total: plan.total,
- })}
-
-
- {current.content}
-
-
-
-
- {entries.map((entry, index) => (
-
-
-
+
+
+ setExpanded((current_) => !current_)),
+ fillMaxWidth(),
+ defaultMinSize({ minHeight: 48 }),
+ ]}
+ >
+
+
+ {t('stepLabel', { current: plan.currentIndex + 1, total: plan.total })}
+
+
+ {current.content}
+
+
+ {expanded ? (
+
+ {entries.map((entry, index) => (
+
- {entry.content}
-
-
- ))}
-
-
-
+
+ {STATUS_GLYPH[entry.status]}
+
+
+ {entry.content}
+
+
+ ))}
+
+ ) : null}
+
+
);
}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/prompt-dock.tsx b/apps/mobile/src/components/conversation/prompt-dock/prompt-dock.tsx
index 1098ff5cb..d043f0feb 100644
--- a/apps/mobile/src/components/conversation/prompt-dock/prompt-dock.tsx
+++ b/apps/mobile/src/components/conversation/prompt-dock/prompt-dock.tsx
@@ -1,9 +1,9 @@
import type { PermissionOutcome, QuestionOutcome } from '@linkcode/schema';
import type { CurrentPlan, PromptConversationItem } from '@linkcode/ui/native';
-import { useThemeColor } from 'heroui-native';
-import { ChevronLeftIcon, ChevronRightIcon } from 'lucide-react-native';
+import { NativeIconButton } from '@mobile/components/form/icon-button';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import { useState } from 'react';
-import { Pressable, Text, View } from 'react-native';
+import { Text, View } from 'react-native';
import { useTranslations } from 'use-intl';
import { PermissionPrompt } from './permission-prompt';
import { PlanTracker } from './plan-tracker';
@@ -69,8 +69,9 @@ function PermissionPager({
onRespond: (requestId: string, outcome: PermissionOutcome) => void;
}): React.ReactNode {
const t = useTranslations('mobile.chat');
+ const tPermission = useTranslations('workbench.prompt');
const [page, setPage] = useState(0);
- const muted = useThemeColor('muted');
+ const palette = useNativePalette();
const pageIndex = Math.min(page, approvals.length - 1);
const approval = approvals[pageIndex];
@@ -78,25 +79,21 @@ function PermissionPager({
{approvals.length > 1 ? (
- setPage(pageIndex - 1)}
- className="size-8 items-center justify-center"
- >
-
-
-
+ />
+
{t('questionProgress', { current: pageIndex + 1, total: approvals.length })}
- = approvals.length - 1}
onPress={() => setPage(pageIndex + 1)}
- className="size-8 items-center justify-center"
- >
-
-
+ />
) : null}
{
+ const text = customText.get().trim();
+ onAdvance(text ? { selected: [], customText: text } : { ...draft, customText: '' });
+ };
+
+ return (
+
+
+
+
+ {current > 1 ? (
+
+
+ {question.options.map((option) => {
+ const selected = draft.selected.includes(option.optionId);
+ return (
+ onSelectOption(option.optionId)}
+ modifiers={[
+ buttonStyle('plain'),
+ disabled(responding),
+ accessibilityLabel(option.label),
+ ]}
+ >
+
+
+
+ {option.label}
+ {option.description ? (
+
+ {option.description}
+
+ ) : null}
+
+
+
+
+ );
+ })}
+
+ {
+ if (text.trim() && draft.selected.length > 0) {
+ onDraftChange({ selected: [], customText: text });
+ }
+ }}
+ />
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/question-page.tsx b/apps/mobile/src/components/conversation/prompt-dock/question-page.tsx
new file mode 100644
index 000000000..7cc0bd3e6
--- /dev/null
+++ b/apps/mobile/src/components/conversation/prompt-dock/question-page.tsx
@@ -0,0 +1,164 @@
+import {
+ Button,
+ Checkbox,
+ Column,
+ OutlinedTextField,
+ RadioButton,
+ Row,
+ Text,
+ TextButton,
+ useNativeState,
+} from '@expo/ui/jetpack-compose';
+import {
+ defaultMinSize,
+ fillMaxWidth,
+ selectable,
+ selectableGroup,
+ toggleable,
+ weight,
+} from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { useTranslations } from 'use-intl';
+import type { QuestionPageProps } from './question-page.types';
+
+export function QuestionPage({
+ question,
+ draft,
+ current,
+ total,
+ isLast,
+ responding,
+ onDraftChange,
+ onAdvance,
+ onPrevious,
+ onSelectOption,
+ onCancel,
+ onClose,
+}: QuestionPageProps & { onClose: () => void }): React.ReactNode {
+ const t = useTranslations('mobile.chat');
+ const tQuestion = useTranslations('workbench.question');
+ const colors = useAppMaterialColors();
+ // Each page owns a distinct native state; the orchestrator remounts pages by key so a stale
+ // native write from the previous question cannot leak here.
+ const customText = useNativeState(draft.customText);
+
+ const advance = (): void => {
+ const text = customText.get().trim();
+ onAdvance(text ? { selected: [], customText: text } : { ...draft, customText: '' });
+ };
+
+ return (
+
+
+ {current > 1 ? (
+ onPrevious({ ...draft, customText: customText.get() })}
+ >
+ {tQuestion('previous')}
+
+ ) : null}
+
+ {t('questionProgress', { current, total })}
+
+ {
+ onDraftChange({ ...draft, customText: customText.get() });
+ onClose();
+ }}
+ >
+ {t('close')}
+
+
+
+ {question.prompt}
+
+
+ {question.multiSelect ? tQuestion('instructionMultiple') : tQuestion('instructionSingle')}
+
+
+
+ {question.options.map((option) => {
+ const selected = draft.selected.includes(option.optionId);
+ return (
+ {
+ customText.set('');
+ onSelectOption(option.optionId);
+ },
+ {
+ role: 'checkbox',
+ },
+ )
+ : selectable(
+ selected,
+ () => {
+ customText.set('');
+ onSelectOption(option.optionId);
+ },
+ 'radioButton',
+ ),
+ ]),
+ ]}
+ >
+ {question.multiSelect ? (
+
+ ) : (
+
+ )}
+
+
+ {option.label}
+
+ {option.description ? (
+
+ {option.description}
+
+ ) : null}
+
+
+ );
+ })}
+
+ {
+ onDraftChange({ selected: text.trim() ? [] : draft.selected, customText: text });
+ }}
+ modifiers={[fillMaxWidth()]}
+ >
+
+ {t('customAnswerPlaceholder')}
+
+
+
+ {isLast ? t('submitAnswers') : t('next')}
+
+
+ {tQuestion('dismiss')}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/question-page.types.ts b/apps/mobile/src/components/conversation/prompt-dock/question-page.types.ts
new file mode 100644
index 000000000..0b04b501b
--- /dev/null
+++ b/apps/mobile/src/components/conversation/prompt-dock/question-page.types.ts
@@ -0,0 +1,20 @@
+import type { Question } from '@linkcode/schema';
+
+export interface QuestionDraft {
+ selected: string[];
+ customText: string;
+}
+
+export interface QuestionPageProps {
+ question: Question;
+ draft: QuestionDraft;
+ current: number;
+ total: number;
+ isLast: boolean;
+ responding: boolean;
+ onDraftChange: (draft: QuestionDraft) => void;
+ onAdvance: (draft: QuestionDraft) => void;
+ onPrevious: (draft: QuestionDraft) => void;
+ onSelectOption: (optionId: string) => void;
+ onCancel: () => void;
+}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/question-prompt.ios.tsx b/apps/mobile/src/components/conversation/prompt-dock/question-prompt.ios.tsx
new file mode 100644
index 000000000..952f1a1ef
--- /dev/null
+++ b/apps/mobile/src/components/conversation/prompt-dock/question-prompt.ios.tsx
@@ -0,0 +1,39 @@
+import type { Question, QuestionOutcome } from '@linkcode/schema';
+import { QuestionPage } from './question-page.ios';
+import { useQuestionResponse } from './use-question-response';
+
+/**
+ * One agent question batch pages within its card (desktop `question-prompt.tsx`): tappable
+ * option rows draw their own selection state, `multiSelect` toggles, single-select
+ * auto-advances, an optional free-text answer, and the whole batch resolves as one
+ * `QuestionOutcome`. The page rendering is platform-split; this orchestrator is shared.
+ */
+export function QuestionPrompt({
+ questions,
+ responding,
+ onRespond,
+}: {
+ questions: Question[];
+ responding: boolean;
+ onRespond: (outcome: QuestionOutcome) => void;
+}): React.ReactNode {
+ const { question, draft, current, total, isLast, setDraft, advance, previous, selectOption } =
+ useQuestionResponse(questions, responding, onRespond);
+
+ return (
+ onRespond({ outcome: 'cancelled' })}
+ />
+ );
+}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/question-prompt.tsx b/apps/mobile/src/components/conversation/prompt-dock/question-prompt.tsx
index 0fda9748c..7ad2ddb42 100644
--- a/apps/mobile/src/components/conversation/prompt-dock/question-prompt.tsx
+++ b/apps/mobile/src/components/conversation/prompt-dock/question-prompt.tsx
@@ -1,42 +1,19 @@
+import { Column, FilledTonalButton, ModalBottomSheet, Row, Text } from '@expo/ui/jetpack-compose';
import {
- Button,
- Host,
- HStack,
- Image,
- Spacer,
- Text,
- TextField,
- useNativeState,
- VStack,
-} from '@expo/ui/swift-ui';
-import {
- buttonStyle,
- contentShape,
- disabled,
- font,
- foregroundStyle,
- onTapGesture,
- shapes,
-} from '@expo/ui/swift-ui/modifiers';
-import type { Question, QuestionAnswer, QuestionOutcome } from '@linkcode/schema';
+ fillMaxWidth,
+ paddingAll,
+ verticalScroll,
+ weight,
+} from '@expo/ui/jetpack-compose/modifiers';
+import type { Question, QuestionOutcome } from '@linkcode/schema';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
import { useState } from 'react';
-import { View } from 'react-native';
+import { Alert } from 'react-native';
import { useTranslations } from 'use-intl';
+import { QuestionPage } from './question-page';
+import { useQuestionResponse } from './use-question-response';
-const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
-const TERTIARY = foregroundStyle({ type: 'hierarchical', style: 'tertiary' });
-const WHOLE_ROW = contentShape(shapes.rectangle());
-
-interface Draft {
- selected: string[];
- customText: string;
-}
-
-/**
- * One agent question batch pages within its card (desktop `question-prompt.tsx`): tappable
- * option rows draw their own checkmark, `multiSelect` toggles, single-select auto-advances,
- * an optional free-text answer, and the whole batch resolves as one `QuestionOutcome`.
- */
export function QuestionPrompt({
questions,
responding,
@@ -46,173 +23,74 @@ export function QuestionPrompt({
responding: boolean;
onRespond: (outcome: QuestionOutcome) => void;
}): React.ReactNode {
- const [index, setIndex] = useState(0);
- const [drafts, setDrafts] = useState>({});
-
- const question = questions[Math.min(index, questions.length - 1)];
- const draft = drafts[question.questionId] ?? { selected: [], customText: '' };
- const isLast = index >= questions.length - 1;
-
- const setDraft = (next: Draft): void => {
- setDrafts((current) => ({ ...current, [question.questionId]: next }));
- };
-
- const buildAnswers = (finalDraft: Draft): QuestionAnswer[] =>
- questions.map((entry) => {
- const value =
- entry.questionId === question.questionId
- ? finalDraft
- : (drafts[entry.questionId] ?? { selected: [], customText: '' });
- const text = value.customText.trim();
- return {
- questionId: entry.questionId,
- selectedOptionIds: value.selected,
- ...(text.length > 0 && { customText: text }),
- };
- });
-
- const advanceOrSubmit = (finalDraft: Draft): void => {
- if (isLast) {
- onRespond({ outcome: 'answered', answers: buildAnswers(finalDraft) });
- } else {
- setDraft(finalDraft);
- setIndex((current) => current + 1);
- }
- };
-
- return (
- onRespond({ outcome: 'cancelled' })}
- />
- );
-}
-
-function QuestionPage({
- question,
- draft,
- current,
- total,
- isLast,
- responding,
- onDraftChange,
- onAdvance,
- onCancel,
-}: {
- question: Question;
- draft: Draft;
- current: number;
- total: number;
- isLast: boolean;
- responding: boolean;
- onDraftChange: (draft: Draft) => void;
- onAdvance: (draft: Draft) => void;
- onCancel: () => void;
-}): React.ReactNode {
+ const [open, setOpen] = useState(false);
+ const colors = useAppMaterialColors();
const t = useTranslations('mobile.chat');
- // Each page owns a distinct native state. Remounting after a structured selection guarantees
- // that an asynchronous native write from the previous question/answer mode cannot leak here.
- const customText = useNativeState(draft.customText);
-
- const toggleOption = (optionId: string): void => {
- if (responding) return;
- const selected = question.multiSelect
- ? draft.selected.includes(optionId)
- ? draft.selected.filter((id) => id !== optionId)
- : [...draft.selected, optionId]
- : [optionId];
- const next = { selected, customText: '' };
- onDraftChange(next);
- if (!question.multiSelect) onAdvance(next);
- };
-
- const advance = (): void => {
- const text = customText.get().trim();
- onAdvance(text ? { selected: [], customText: text } : { ...draft, customText: '' });
- };
-
+ const tq = useTranslations('workbench.question');
+ const { question, draft, current, total, isLast, setDraft, advance, previous, selectOption } =
+ useQuestionResponse(questions, responding, onRespond);
return (
-
-
-
-
- {question.header ? (
-
- {question.header}
-
- ) : null}
-
+ <>
+
+
+
+
+ {tq('callTitle')}
+
+
{question.prompt}
-
- {total > 1 ? (
-
- {t('questionProgress', { current, total })}
-
- ) : null}
- {
- if (!responding) onCancel();
- }),
- ]}
- />
-
-
- {question.options.map((option) => {
- const selected = draft.selected.includes(option.optionId);
- return (
- toggleOption(option.optionId))]}
- >
-
-
- {option.label}
- {option.description ? (
-
- {option.description}
-
- ) : null}
-
-
-
- );
- })}
-
- {
- if (text.trim() && draft.selected.length > 0) {
- onDraftChange({ selected: [], customText: text });
- }
- }}
- />
-
-
-
-
+
+ setOpen(true)}>
+ {t('answer')}
+
+
+
+ {open ? (
+
+ setOpen(false)}
+ skipPartiallyExpanded
+ contentColor={colors.onSurface}
+ >
+
+ setOpen(false)}
+ onCancel={() =>
+ Alert.alert(tq('dismissConfirmTitle'), tq('dismissConfirmDescription'), [
+ { text: tq('dismissConfirmCancel'), style: 'cancel' },
+ {
+ text: tq('dismissConfirmAction'),
+ style: 'destructive',
+ onPress: () => onRespond({ outcome: 'cancelled' }),
+ },
+ ])
+ }
+ />
+
+
+
+ ) : null}
+ >
);
}
diff --git a/apps/mobile/src/components/conversation/prompt-dock/use-question-response.ts b/apps/mobile/src/components/conversation/prompt-dock/use-question-response.ts
new file mode 100644
index 000000000..e79b554fb
--- /dev/null
+++ b/apps/mobile/src/components/conversation/prompt-dock/use-question-response.ts
@@ -0,0 +1,74 @@
+import type { Question, QuestionOutcome } from '@linkcode/schema';
+import { useState } from 'react';
+import type { QuestionDraft } from './question-page.types';
+
+export function useQuestionResponse(
+ questions: Question[],
+ responding: boolean,
+ onRespond: (outcome: QuestionOutcome) => void,
+) {
+ const [index, setIndex] = useState(0);
+ const [drafts, setDrafts] = useState>({});
+ const question = questions[index];
+ const draft = drafts[question.questionId] ?? { selected: [], customText: '' };
+ const isLast = index === questions.length - 1;
+
+ const setDraft = (next: QuestionDraft): void => {
+ if (responding) return;
+ setDrafts((current) => ({ ...current, [question.questionId]: next }));
+ };
+
+ const advance = (next: QuestionDraft): void => {
+ if (responding) return;
+ setDraft(next);
+ if (!isLast) {
+ setIndex((current) => current + 1);
+ return;
+ }
+ onRespond({
+ outcome: 'answered',
+ answers: questions.map((entry) => {
+ const value =
+ entry.questionId === question.questionId
+ ? next
+ : (drafts[entry.questionId] ?? { selected: [], customText: '' });
+ const customText = value.customText.trim();
+ return {
+ questionId: entry.questionId,
+ selectedOptionIds: customText ? [] : value.selected,
+ ...(customText && { customText }),
+ };
+ }),
+ });
+ };
+
+ const selectOption = (optionId: string): void => {
+ if (responding) return;
+ const selected = question.multiSelect
+ ? draft.selected.includes(optionId)
+ ? draft.selected.filter((id) => id !== optionId)
+ : [...draft.selected, optionId]
+ : [optionId];
+ const next = { selected, customText: '' };
+ if (question.multiSelect) setDraft(next);
+ else advance(next);
+ };
+
+ const previous = (next: QuestionDraft): void => {
+ if (responding || index === 0) return;
+ setDraft(next);
+ setIndex((current) => current - 1);
+ };
+
+ return {
+ question,
+ draft,
+ current: index + 1,
+ total: questions.length,
+ isLast,
+ setDraft,
+ advance,
+ previous,
+ selectOption,
+ };
+}
diff --git a/apps/mobile/src/components/conversation/send-button.tsx b/apps/mobile/src/components/conversation/send-button.tsx
index 8c75b1779..5d4d703ad 100644
--- a/apps/mobile/src/components/conversation/send-button.tsx
+++ b/apps/mobile/src/components/conversation/send-button.tsx
@@ -1,10 +1,8 @@
-import { useThemeColor } from 'heroui-native';
-import { ArrowUpIcon, SquareIcon } from 'lucide-react-native';
-import { Pressable } from 'react-native';
+import { NativeIconButton } from '@mobile/components/form/icon-button';
/** The round send action shared by the conversation composer and the new-thread draft: one
* circular button that morphs between send and stop, because a turn in flight is the only thing
- * the user wants to do to it. */
+ * the user wants to do to it. Filled with the platform tint, like the native senders. */
export function SendButton({
isRunning,
enabled,
@@ -20,28 +18,13 @@ export function SendButton({
onSend: () => void;
onStop: () => void;
}): React.ReactNode {
- const [muted, background, foreground] = useThemeColor(['muted', 'background', 'foreground']);
- const ActionIcon = isRunning ? SquareIcon : ArrowUpIcon;
-
return (
- ({
- backgroundColor: enabled ? foreground : muted,
- opacity: pressed ? 0.6 : 1,
- })}
- >
-
-
+ />
);
}
diff --git a/apps/mobile/src/components/conversation/session-title.tsx b/apps/mobile/src/components/conversation/session-title.tsx
index 36cf1b448..871afb77c 100644
--- a/apps/mobile/src/components/conversation/session-title.tsx
+++ b/apps/mobile/src/components/conversation/session-title.tsx
@@ -1,5 +1,5 @@
import type { SessionStatus } from '@linkcode/schema';
-import { useThemeColor } from 'heroui-native';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import { Text, View } from 'react-native';
/** Header title with the session's status as a dot — the same 8pt dot and palette as the thread
@@ -11,18 +11,22 @@ export function SessionTitle({
title: string;
status: SessionStatus | null;
}): React.ReactNode {
- const [success, warning, muted] = useThemeColor(['success', 'warning', 'muted']);
+ const palette = useNativePalette();
const color =
status === 'running'
- ? success
+ ? palette.success
: status === 'starting' || status === 'awaiting-input'
- ? warning
- : muted;
+ ? palette.warning
+ : palette.outline;
return (
{/* `headline` is the 17pt semibold metric UIKit draws inline nav titles with. */}
-
+
{title}
{status ? : null}
diff --git a/apps/mobile/src/components/conversation/session-tools.ios.tsx b/apps/mobile/src/components/conversation/session-tools.ios.tsx
new file mode 100644
index 000000000..83575408f
--- /dev/null
+++ b/apps/mobile/src/components/conversation/session-tools.ios.tsx
@@ -0,0 +1,124 @@
+import { Host, Menu, Picker, Text as UIText } from '@expo/ui/swift-ui';
+import { tag } from '@expo/ui/swift-ui/modifiers';
+import { AgentIcon, groupModelsByProvider, modelChoiceKey } from '@linkcode/ui/native';
+import { OptionChip } from '@mobile/components/conversation/option-chip.ios';
+import type {
+ SessionApprovalChipProps,
+ SessionSelectorChipProps,
+} from '@mobile/components/conversation/session-tools.types';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { View } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+/** The live session's approval chip: a ghost shield opening the adapter-advertised policy menu.
+ * Absent state means the adapter has no switchable policies, so nothing renders. Selection is
+ * server-reflected — the checkmark moves once `approval-policy-update` echoes the switch. */
+export function SessionApprovalChip({
+ approvalPolicy,
+ onPolicyChange,
+}: SessionApprovalChipProps): React.ReactNode {
+ const t = useTranslations('mobile.sessions');
+ if (!approvalPolicy || approvalPolicy.availablePolicies.length === 0) return null;
+
+ // eslint-disable-next-line vibe-proof/react-no-performance-impacting-array-find -- one lookup against a handful of policies per render
+ const current = approvalPolicy.availablePolicies.find(
+ (policy) => policy.policyId === approvalPolicy.currentPolicyId,
+ );
+ return (
+
+
+
+ {approvalPolicy.availablePolicies.map((policy) => (
+
+ {policy.name}
+
+ ))}
+
+
+
+ );
+}
+
+/** The live session's selector: the harness brand mark beside one menu with Model / Effort
+ * submenus. No harness submenu (the agent is fixed) and no "Default" entries — a running session
+ * is always on a concrete value, and the checkmarks follow the `model-update` / `effort-update`
+ * echoes rather than any local pick. */
+export function SessionSelectorChip({
+ kind,
+ selectorValue,
+ models,
+ currentModelKey,
+ onModelChange,
+ effortOptions,
+ currentEffort,
+ onEffortChange,
+}: SessionSelectorChipProps): React.ReactNode {
+ const t = useTranslations('mobile.sessions');
+ const palette = useNativePalette();
+ const hasModels = models !== null && models.length > 0;
+ const hasEfforts = effortOptions !== undefined && effortOptions.length > 0;
+ if (!hasModels && !hasEfforts) return null;
+
+ // The account label joins a row only when the list spans several accounts — the same threshold
+ // as the web's provider grouping; a single-account list repeating its account is noise.
+ const spansAccounts = models !== null && groupModelsByProvider(models) !== null;
+
+ return (
+
+ {/* Footnote's 13pt metric, so the mark scales with the chip text beside it. */}
+
+
+ {/* Keyed by menu shape: children added to an already-created Menu never reach the
+ native UIMenu, so late-loading models/efforts must remount the chip. */}
+
+ {hasModels ? (
+
+ ) : null}
+ {hasEfforts ? (
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/session-tools.tsx b/apps/mobile/src/components/conversation/session-tools.tsx
index e09721a6e..b15ee4463 100644
--- a/apps/mobile/src/components/conversation/session-tools.tsx
+++ b/apps/mobile/src/components/conversation/session-tools.tsx
@@ -1,54 +1,68 @@
-import { Host, Menu, Picker, Text as UIText } from '@expo/ui/swift-ui';
-import { tag } from '@expo/ui/swift-ui/modifiers';
-import type { AgentKind, ApprovalPolicyState, EffortLevel } from '@linkcode/schema';
-import type { EffortOption, ModelOption } from '@linkcode/ui/native';
-import { AgentIcon, groupModelsByProvider, modelChoiceKey } from '@linkcode/ui/native';
-import { OptionChip } from '@mobile/components/conversation/option-chip';
-import { useThemeColor } from 'heroui-native';
+import { AgentIcon, modelChoiceKey } from '@linkcode/ui/native';
+import type {
+ SessionApprovalChipProps,
+ SessionSelectorChipProps,
+} from '@mobile/components/conversation/session-tools.types';
+import { ToolChip } from '@mobile/components/conversation/tool-chip.android';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { SelectorSheet } from '@mobile/components/form/selector-sheet.android';
+import type {
+ SelectorChipAxis,
+ SelectorModelAxis,
+} from '@mobile/components/form/selector-sheet.types';
+import { modelAxisGroups } from '@mobile/components/form/selector-sheet.types';
+import { SheetPicker } from '@mobile/components/form/sheet-picker.android';
+import { useState } from 'react';
import { View } from 'react-native';
import { useTranslations } from 'use-intl';
-/** The live session's approval chip: a ghost shield opening the adapter-advertised policy menu.
- * Absent state means the adapter has no switchable policies, so nothing renders. Selection is
- * server-reflected — the checkmark moves once `approval-policy-update` echoes the switch. */
+/** The live session's approval chip on Android. Selection is server-reflected — the radio moves
+ * once `approval-policy-update` echoes the switch, so a rejected switch just leaves the old
+ * value showing. */
export function SessionApprovalChip({
approvalPolicy,
onPolicyChange,
-}: {
- approvalPolicy: ApprovalPolicyState | null;
- onPolicyChange: (policyId: string) => void;
-}): React.ReactNode {
+}: SessionApprovalChipProps): React.ReactNode {
const t = useTranslations('mobile.sessions');
+ const [open, setOpen] = useState(false);
if (!approvalPolicy || approvalPolicy.availablePolicies.length === 0) return null;
// eslint-disable-next-line vibe-proof/react-no-performance-impacting-array-find -- one lookup against a handful of policies per render
const current = approvalPolicy.availablePolicies.find(
(policy) => policy.policyId === approvalPolicy.currentPolicyId,
);
+
return (
-
-
+
-
- {approvalPolicy.availablePolicies.map((policy) => (
-
- {policy.name}
-
- ))}
-
-
-
+ onPress={() => setOpen(true)}
+ />
+ setOpen(false)}
+ sections={[
+ {
+ id: 'policy',
+ title: t('approvalLabel'),
+ selection: approvalPolicy.currentPolicyId,
+ options: approvalPolicy.availablePolicies.map((policy) => ({
+ id: policy.policyId,
+ label: policy.name,
+ })),
+ onSelect: onPolicyChange,
+ },
+ ]}
+ />
+ >
);
}
-/** The live session's selector: the harness brand mark beside one menu with Model / Effort
- * submenus. No harness submenu (the agent is fixed) and no "Default" entries — a running session
- * is always on a concrete value, and the checkmarks follow the `model-update` / `effort-update`
- * echoes rather than any local pick. */
+/** The live session's selector on Android: the structured `SelectorSheet` with Model / Effort
+ * axes, no harness axis (the agent is fixed) and no "Default" entries — a running session is
+ * always on a concrete value. Selections are server-reflected like the approval chip. */
export function SessionSelectorChip({
kind,
selectorValue,
@@ -58,80 +72,53 @@ export function SessionSelectorChip({
effortOptions,
currentEffort,
onEffortChange,
-}: {
- kind: AgentKind;
- /** Text on the chip — the running model (and effort), or a placeholder. */
- selectorValue: string;
- /** Account-backed options; null while loading (model submenu hidden). */
- models: ModelOption[] | null;
- /** `modelChoiceKey` of the entry matching the running model, or null while unknown. */
- currentModelKey: string | null;
- onModelChange: (model: ModelOption) => void;
- effortOptions: EffortOption[] | undefined;
- currentEffort: EffortLevel | null;
- onEffortChange: (effort: EffortLevel) => void;
-}): React.ReactNode {
+}: SessionSelectorChipProps): React.ReactNode {
const t = useTranslations('mobile.sessions');
- const muted = useThemeColor('muted');
- const hasModels = models !== null && models.length > 0;
- const hasEfforts = effortOptions !== undefined && effortOptions.length > 0;
- if (!hasModels && !hasEfforts) return null;
+ const colors = useAppMaterialColors();
+ const [open, setOpen] = useState(false);
- // The account label joins a row only when the list spans several accounts — the same threshold
- // as the web's provider grouping; a single-account list repeating its account is noise.
- const spansAccounts = models !== null && groupModelsByProvider(models) !== null;
+ let modelAxis: SelectorModelAxis | undefined;
+ if (models !== null && models.length > 0) {
+ modelAxis = {
+ title: t('modelLabel'),
+ selection: currentModelKey,
+ groups: modelAxisGroups(models),
+ onSelect(key) {
+ const option = models.find((model) => modelChoiceKey(model) === key);
+ if (option) onModelChange(option);
+ },
+ };
+ }
+ let effortAxis: SelectorChipAxis | undefined;
+ if (effortOptions !== undefined && effortOptions.length > 0) {
+ effortAxis = {
+ title: t('effortLabel'),
+ selection: currentEffort,
+ options: effortOptions.map((option) => ({ id: option.id, label: option.label })),
+ onSelect(value) {
+ const option = effortOptions.find((candidate) => candidate.id === value);
+ if (option) onEffortChange(option.id);
+ },
+ };
+ }
+ if (modelAxis === undefined && effortAxis === undefined) return null;
return (
{/* Footnote's 13pt metric, so the mark scales with the chip text beside it. */}
-
-
- {/* Keyed by menu shape: children added to an already-created Menu never reach the
- native UIMenu, so late-loading models/efforts must remount the chip. */}
-
- {hasModels ? (
-
- ) : null}
- {hasEfforts ? (
-
- ) : null}
-
-
+
+ setOpen(true)}
+ />
+ setOpen(false)}
+ model={modelAxis}
+ effort={effortAxis}
+ />
);
}
diff --git a/apps/mobile/src/components/conversation/session-tools.types.ts b/apps/mobile/src/components/conversation/session-tools.types.ts
new file mode 100644
index 000000000..f5d3acea8
--- /dev/null
+++ b/apps/mobile/src/components/conversation/session-tools.types.ts
@@ -0,0 +1,21 @@
+import type { AgentKind, ApprovalPolicyState, EffortLevel } from '@linkcode/schema';
+import type { EffortOption, ModelOption } from '@linkcode/ui/native';
+
+export interface SessionApprovalChipProps {
+ approvalPolicy: ApprovalPolicyState | null;
+ onPolicyChange: (policyId: string) => void;
+}
+
+export interface SessionSelectorChipProps {
+ kind: AgentKind;
+ /** Text on the chip — the running model (and effort), or a placeholder. */
+ selectorValue: string;
+ /** Account-backed options; null while loading (model submenu hidden). */
+ models: ModelOption[] | null;
+ /** `modelChoiceKey` of the entry matching the running model, or null while unknown. */
+ currentModelKey: string | null;
+ onModelChange: (model: ModelOption) => void;
+ effortOptions: EffortOption[] | undefined;
+ currentEffort: EffortLevel | null;
+ onEffortChange: (effort: EffortLevel) => void;
+}
diff --git a/apps/mobile/src/components/conversation/timeline-item.tsx b/apps/mobile/src/components/conversation/timeline-item.tsx
index 045d8a4e5..d6ba4aae4 100644
--- a/apps/mobile/src/components/conversation/timeline-item.tsx
+++ b/apps/mobile/src/components/conversation/timeline-item.tsx
@@ -1,11 +1,11 @@
import type { ConversationItem } from '@linkcode/client-core';
import type { ContentBlock, ToolCall } from '@linkcode/schema';
-import { NativeMarkdown } from '@linkcode/ui/native';
-import { Spinner, useThemeColor } from 'heroui-native';
-import { ChevronDownIcon, ChevronRightIcon, CircleAlertIcon } from 'lucide-react-native';
-import { useState } from 'react';
-import { Pressable, Text, View } from 'react-native';
+import { answerText, NativeMarkdown } from '@linkcode/ui/native';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { Text, View } from 'react-native';
import { useTranslations } from 'use-intl';
+import { ReasoningRow, ToolRow } from './activity-row';
+import { TranscriptRecord } from './transcript-record';
function blocksToText(blocks: ContentBlock[]): string {
return blocks
@@ -14,13 +14,6 @@ function blocksToText(blocks: ContentBlock[]): string {
.trim();
}
-const PLAN_STATUS_MARK = {
- pending: '○',
- in_progress: '◐',
- completed: '●',
- cancelled: '×',
-} as const;
-
/** Compact token counts ("193437" → "193.4k") — mirrors the web CompactionMarker's format. */
function formatTokens(count: number): string {
if (count >= 1_000_000) return `${(count / 1_000_000).toFixed(1)}M`;
@@ -38,13 +31,20 @@ export function TimelineItem({
onPressTool?: (toolCall: ToolCall) => void;
}): React.ReactNode {
const t = useTranslations('mobile.conversation');
+ const tq = useTranslations('workbench.question');
+ const palette = useNativePalette();
switch (item.kind) {
case 'message':
return item.role === 'user' ? (
-
- {blocksToText(item.blocks)}
+
+
+ {blocksToText(item.blocks)}
+
) : (
@@ -54,8 +54,12 @@ export function TimelineItem({
// eslint-disable-next-line @eslint-react/no-array-index-key -- blocks carry no ids; the array only ever appends while streaming
) : (
- // eslint-disable-next-line @eslint-react/no-array-index-key -- see above
-
+
[{block.type}]
),
@@ -73,65 +77,76 @@ export function TimelineItem({
/>
);
case 'plan':
- return (
-
- {t('plan')}
- {item.plan.entries.map((entry, index) => (
-
- {PLAN_STATUS_MARK[entry.status]} {entry.content}
-
- ))}
-
- );
- case 'approval':
+ return null;
+ case 'approval': {
// Pending asks are answered from the prompt dock; the timeline records only resolved ones.
if (!item.resolution) return null;
+ const outcome = item.resolution.outcome;
+ const optionNames = new Map(item.options.map((option) => [option.optionId, option.name]));
return (
-
- {t('approval')}
- {item.toolCall.title ?? ''}
-
+
);
- case 'question':
+ }
+ case 'question': {
if (!item.resolution) return null;
+ const outcome = item.resolution.outcome;
+ const answers = new Map(
+ (outcome.outcome === 'answered' ? outcome.answers : []).map((answer) => [
+ answer.questionId,
+ answer,
+ ]),
+ );
return (
-
- {t('question')}
- {item.questions.map((question) => (
-
- {question.prompt}
-
- ))}
-
+ {
+ const answer = answers.get(question.questionId);
+ return {
+ id: question.questionId,
+ label: question.prompt,
+ value: answer ? (answerText(question, answer) ?? tq('skipped')) : undefined,
+ };
+ })}
+ />
);
+ }
case 'error':
return (
-
- {t('error')}
- {item.message}
-
+
);
case 'compaction':
if (item.status === 'in_progress') {
return (
- {t('compacting')}
+
+ {t('compacting')}
+
);
}
return (
- {t('compacted')}
+
+ {t('compacted')}
+
{item.preTokens !== undefined && item.postTokens !== undefined ? (
-
+
{t('compactedTokens', {
pre: formatTokens(item.preTokens),
post: formatTokens(item.postTokens),
@@ -144,59 +159,3 @@ export function TimelineItem({
return null;
}
}
-
-function ReasoningRow({ text, streaming }: { text: string; streaming: boolean }): React.ReactNode {
- const t = useTranslations('mobile.conversation');
- const muted = useThemeColor('muted');
- const [open, setOpen] = useState(false);
- const Chevron = open ? ChevronDownIcon : ChevronRightIcon;
-
- return (
-
- setOpen((current) => !current)}
- >
- {t('reasoning')}
- {streaming ? : }
-
- {open ? {text} : null}
-
- );
-}
-
-/** A quiet, collapsed summary line — the chevron sits against the label rather than at the
- * row's trailing edge, so the row reads as one phrase. A failed call is common and often
- * expected here, so it stays in the muted voice and is marked by an icon rather than by
- * colouring the whole line; the icon is also what keeps the status off colour alone. */
-function ToolRow({
- title,
- status,
- onPress,
-}: {
- title: string;
- status: 'pending' | 'in_progress' | 'completed' | 'failed';
- onPress?: () => void;
-}): React.ReactNode {
- const [muted, danger] = useThemeColor(['muted', 'danger']);
-
- return (
-
-
- {title}
-
- {status === 'failed' ? : null}
- {status === 'in_progress' ? (
-
- ) : (
-
- )}
-
- );
-}
diff --git a/apps/mobile/src/components/conversation/tool-chip.android.tsx b/apps/mobile/src/components/conversation/tool-chip.android.tsx
new file mode 100644
index 000000000..e2f74722a
--- /dev/null
+++ b/apps/mobile/src/components/conversation/tool-chip.android.tsx
@@ -0,0 +1,47 @@
+import { Text, TextButton } from '@expo/ui/jetpack-compose';
+import { defaultMinSize, fillMaxWidth } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { NativeIconButton } from '@mobile/components/form/icon-button';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+
+/** Native Material controls open the composer's model and approval sheets. */
+export function ToolChip({
+ label,
+ value,
+ iconOnly = false,
+ maxValueWidth,
+ onPress,
+}: {
+ /** Accessibility name of the chip ("Model"); not rendered. */
+ label: string;
+ /** Resolved current value — rendered unless `iconOnly`, always spoken. */
+ value: string;
+ /** Icon-size the chip (the shield); the value still reaches accessibility. */
+ iconOnly?: boolean;
+ /** Cap for long values so one chip cannot push the send button out. */
+ maxValueWidth?: number;
+ onPress: () => void;
+}): React.ReactNode {
+ const colors = useAppMaterialColors();
+
+ if (iconOnly) {
+ return ;
+ }
+
+ return (
+
+
+
+ {value}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.ios.tsx b/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.ios.tsx
new file mode 100644
index 000000000..dd42f0e4b
--- /dev/null
+++ b/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.ios.tsx
@@ -0,0 +1,62 @@
+import { HStack, ScrollView, Spacer, Text, VStack } from '@expo/ui/swift-ui';
+import { font, foregroundStyle, lineLimit } from '@expo/ui/swift-ui/modifiers';
+import { diffLines, patchLines } from '@linkcode/ui/native';
+import {
+ DIFF_ADDED_HEX,
+ DIFF_REMOVED_HEX,
+ gutterLine,
+} from '@mobile/components/conversation/tool-detail-sheet/diff-block.shared';
+import { SECONDARY } from '@mobile/components/form/styles.ios';
+
+const MONO_FOOTNOTE = font({ textStyle: 'footnote', design: 'monospaced' });
+const ADDED = foregroundStyle(DIFF_ADDED_HEX);
+const REMOVED = foregroundStyle(DIFF_REMOVED_HEX);
+
+/** Unified-diff card for a single path inside the tool-detail sheet. */
+export function DiffBlock({
+ path,
+ oldText,
+ newText,
+ patch,
+}: {
+ path: string;
+ oldText?: string;
+ newText?: string;
+ /** Unified-patch text; when it parses to rows it wins over the text pair (same precedence
+ * as `diffStats` — codex ships hunk text alongside a patch). */
+ patch?: string;
+}): React.ReactNode {
+ const patchRows = patch === undefined ? undefined : patchLines(patch);
+ const rows =
+ patchRows !== undefined && patchRows.length > 0
+ ? patchRows
+ : diffLines(oldText ?? '', newText ?? '');
+ const additions = rows.filter((row) => row.type === 'add').length;
+ const deletions = rows.filter((row) => row.type === 'del').length;
+
+ return (
+
+
+ {path}
+
+ {additions > 0 ? +{additions} : null}
+ {deletions > 0 ? −{deletions} : null}
+
+
+
+ {rows.map((row) => (
+
+ {gutterLine(row)}
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.shared.ts b/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.shared.ts
new file mode 100644
index 000000000..62ba558f2
--- /dev/null
+++ b/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.shared.ts
@@ -0,0 +1,7 @@
+export const DIFF_ADDED_HEX = '#28A745';
+export const DIFF_REMOVED_HEX = '#D73A49';
+
+export function gutterLine(row: { type: 'add' | 'del' | 'ctx'; text: string }): string {
+ const gutter = row.type === 'add' ? '+' : row.type === 'del' ? '−' : ' ';
+ return `${gutter} ${row.text || ' '}`;
+}
diff --git a/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.tsx b/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.tsx
index 6bac4b203..c88ed3337 100644
--- a/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.tsx
+++ b/apps/mobile/src/components/conversation/tool-detail-sheet/diff-block.tsx
@@ -1,18 +1,16 @@
-import { HStack, ScrollView, Spacer, Text, VStack } from '@expo/ui/swift-ui';
-import { font, foregroundStyle, lineLimit } from '@expo/ui/swift-ui/modifiers';
+import { Column, Row, Spacer, Text } from '@expo/ui/jetpack-compose';
+import { fillMaxWidth, horizontalScroll, weight } from '@expo/ui/jetpack-compose/modifiers';
import { diffLines, patchLines } from '@linkcode/ui/native';
-import { SECONDARY } from '@mobile/components/form/styles';
+import {
+ DIFF_ADDED_HEX,
+ DIFF_REMOVED_HEX,
+ gutterLine,
+} from '@mobile/components/conversation/tool-detail-sheet/diff-block.shared';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
-const MONO_FOOTNOTE = font({ textStyle: 'footnote', design: 'monospaced' });
-const ADDED = foregroundStyle('#28A745');
-const REMOVED = foregroundStyle('#D73A49');
+const MONO = { typography: 'bodySmall', fontFamily: 'monospace' } as const;
-function gutterLine(row: { type: 'add' | 'del' | 'ctx'; text: string }): string {
- const gutter = row.type === 'add' ? '+' : row.type === 'del' ? '−' : ' ';
- return `${gutter} ${row.text || ' '}`;
-}
-
-/** Unified-diff card for a single path inside the tool-detail sheet. */
+/** Android unified-diff card for a single path inside the tool-detail sheet. */
export function DiffBlock({
path,
oldText,
@@ -26,6 +24,7 @@ export function DiffBlock({
* as `diffStats` — codex ships hunk text alongside a patch). */
patch?: string;
}): React.ReactNode {
+ const colors = useAppMaterialColors();
const patchRows = patch === undefined ? undefined : patchLines(patch);
const rows =
patchRows !== undefined && patchRows.length > 0
@@ -35,28 +34,51 @@ export function DiffBlock({
const deletions = rows.filter((row) => row.type === 'del').length;
return (
-
-
- {path}
+
+
+
+ {path}
+
- {additions > 0 ? +{additions} : null}
- {deletions > 0 ? −{deletions} : null}
-
-
-
- {rows.map((row) => (
-
- {gutterLine(row)}
-
- ))}
-
-
-
+ {additions > 0 ? (
+
+ +{additions}
+
+ ) : null}
+ {deletions > 0 ? (
+
+ −{deletions}
+
+ ) : null}
+
+
+ {rows.map((row) => (
+
+ {gutterLine(row)}
+
+ ))}
+
+
);
}
diff --git a/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-content.ts b/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-content.ts
new file mode 100644
index 000000000..bc6ac14de
--- /dev/null
+++ b/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-content.ts
@@ -0,0 +1,28 @@
+import type { ToolCall } from '@linkcode/schema';
+import {
+ stripAnsi,
+ toolCallCommand,
+ toolCallDisplayContent,
+ toolCallFailureMessage,
+ toolCallMetadata,
+} from '@linkcode/ui/native';
+
+/** Everything the sheet renders, derived once for both platform views. Content mirrors desktop's
+ * expanded `ToolCallBody`: metadata badges, diff cards, output, failure message. */
+export function toolDetailContent(toolCall: ToolCall | null): {
+ metadata: ReturnType;
+ contents: ReturnType;
+ failure: string | undefined;
+ command: string | undefined;
+ rawOutput: string | undefined;
+} {
+ const metadata = toolCall ? toolCallMetadata(toolCall) : [];
+ const contents = toolCall ? toolCallDisplayContent(toolCall) : [];
+ const failure = toolCall ? toolCallFailureMessage(toolCall) : undefined;
+ const command = toolCall?.kind === 'execute' ? toolCallCommand(toolCall) : undefined;
+ const rawOutput =
+ toolCall && contents.length === 0 && typeof toolCall.rawOutput === 'string'
+ ? stripAnsi(toolCall.rawOutput)
+ : undefined;
+ return { metadata, contents, failure, command, rawOutput };
+}
diff --git a/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-sheet.ios.tsx b/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-sheet.ios.tsx
new file mode 100644
index 000000000..c3b9848bf
--- /dev/null
+++ b/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-sheet.ios.tsx
@@ -0,0 +1,158 @@
+import {
+ BottomSheet,
+ Group,
+ Host,
+ HStack,
+ Image,
+ ScrollView,
+ Spacer,
+ Text,
+ VStack,
+} from '@expo/ui/swift-ui';
+import {
+ font,
+ foregroundStyle,
+ lineLimit,
+ padding,
+ presentationDetents,
+ presentationDragIndicator,
+} from '@expo/ui/swift-ui/modifiers';
+import type { ToolCall, ToolKind } from '@linkcode/schema';
+import { SECONDARY } from '@mobile/components/form/styles.ios';
+import { useTranslations } from 'use-intl';
+import { DiffBlock } from './diff-block';
+import { toolDetailContent } from './tool-detail-content';
+
+const MONO_FOOTNOTE = font({ textStyle: 'footnote', design: 'monospaced' });
+
+/** SF Symbol per tool kind — the SwiftUI mirror of the timeline's lucide set. */
+const KIND_SYMBOL: Record['systemName']> = {
+ read: 'doc.text',
+ edit: 'square.and.pencil',
+ delete: 'trash',
+ move: 'arrow.turn.down.right',
+ search: 'magnifyingglass',
+ execute: 'terminal',
+ think: 'brain',
+ fetch: 'globe',
+ task: 'cpu',
+ other: 'wrench.and.screwdriver',
+};
+
+/**
+ * Tool-call body host (design §4.3): tapping a tool row opens this sheet instead of expanding
+ * inline, keeping the inverted list's layout stable. Content mirrors desktop's expanded
+ * `ToolCallBody`: metadata badges, diff cards, output, failure message.
+ */
+export function ToolDetailSheet({
+ toolCall,
+ onDismiss,
+}: {
+ /** The call whose body to show; null keeps the sheet dismissed. */
+ toolCall: ToolCall | null;
+ onDismiss: () => void;
+}): React.ReactNode {
+ const t = useTranslations('mobile.chat');
+ const { metadata, contents, failure, command, rawOutput } = toolDetailContent(toolCall);
+
+ return (
+ // The sheet presents its own window, but the anchor itself must live in a SwiftUI Host.
+
+ {
+ if (!presented) onDismiss();
+ }}
+ >
+
+ {toolCall ? (
+
+
+
+
+
+ {toolCall.title}
+
+
+
+ {metadata.length > 0 ? (
+
+ {metadata.map((entry) => (
+
+ {[entry.label ?? entry.key, entry.value].join(' ')}
+
+ ))}
+
+ ) : null}
+ {contents.map((content, index) => {
+ if (content.type === 'diff') {
+ return (
+
+ );
+ }
+ if (content.type === 'content' && content.content.type === 'text') {
+ return (
+
+ {content.content.text}
+
+ );
+ }
+ return null;
+ })}
+ {command || rawOutput ? (
+
+ {command ? (
+ {command}
+ ) : null}
+ {rawOutput ? (
+
+ {rawOutput}
+
+ ) : null}
+
+ ) : null}
+ {failure ? (
+
+ {t('failed')}: {failure}
+
+ ) : null}
+
+
+ ) : (
+
+ )}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-sheet.tsx b/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-sheet.tsx
index e977859dc..4b9837a72 100644
--- a/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-sheet.tsx
+++ b/apps/mobile/src/components/conversation/tool-detail-sheet/tool-detail-sheet.tsx
@@ -1,54 +1,23 @@
+import { Column, ModalBottomSheet, Text } from '@expo/ui/jetpack-compose';
import {
- BottomSheet,
- Group,
- Host,
- HStack,
- Image,
- ScrollView,
- Spacer,
- Text,
- VStack,
-} from '@expo/ui/swift-ui';
-import {
- font,
- foregroundStyle,
- lineLimit,
+ fillMaxWidth,
+ horizontalScroll,
padding,
- presentationDetents,
- presentationDragIndicator,
-} from '@expo/ui/swift-ui/modifiers';
-import type { ToolCall, ToolKind } from '@linkcode/schema';
-import {
- stripAnsi,
- toolCallCommand,
- toolCallDisplayContent,
- toolCallFailureMessage,
- toolCallMetadata,
-} from '@linkcode/ui/native';
-import { SECONDARY } from '@mobile/components/form/styles';
+ verticalScroll,
+} from '@expo/ui/jetpack-compose/modifiers';
+import type { ToolCall } from '@linkcode/schema';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
import { useTranslations } from 'use-intl';
import { DiffBlock } from './diff-block';
+import { toolDetailContent } from './tool-detail-content';
-const MONO_FOOTNOTE = font({ textStyle: 'footnote', design: 'monospaced' });
-
-/** SF Symbol per tool kind — the SwiftUI mirror of the timeline's lucide set. */
-const KIND_SYMBOL: Record['systemName']> = {
- read: 'doc.text',
- edit: 'square.and.pencil',
- delete: 'trash',
- move: 'arrow.turn.down.right',
- search: 'magnifyingglass',
- execute: 'terminal',
- think: 'brain',
- fetch: 'globe',
- task: 'cpu',
- other: 'wrench.and.screwdriver',
-};
+const MONO = { typography: 'bodySmall', fontFamily: 'monospace' } as const;
/**
- * Tool-call body host (design §4.3): tapping a tool row opens this sheet instead of expanding
- * inline, keeping the inverted list's layout stable. Content mirrors desktop's expanded
- * `ToolCallBody`: metadata badges, diff cards, output, failure message.
+ * Android tool-call body sheet (design §4.3): tapping a tool row opens this instead of expanding
+ * inline, keeping the inverted list's layout stable. The SF kind glyph has no Compose asset, so
+ * the title carries the row alone.
*/
export function ToolDetailSheet({
toolCall,
@@ -59,113 +28,89 @@ export function ToolDetailSheet({
onDismiss: () => void;
}): React.ReactNode {
const t = useTranslations('mobile.chat');
- const metadata = toolCall ? toolCallMetadata(toolCall) : [];
- const contents = toolCall ? toolCallDisplayContent(toolCall) : [];
- const failure = toolCall ? toolCallFailureMessage(toolCall) : undefined;
- const command = toolCall?.kind === 'execute' ? toolCallCommand(toolCall) : undefined;
- const rawOutput =
- toolCall && contents.length === 0 && typeof toolCall.rawOutput === 'string'
- ? stripAnsi(toolCall.rawOutput)
- : undefined;
+ const colors = useAppMaterialColors();
+ const { metadata, contents, failure, command, rawOutput } = toolDetailContent(toolCall);
+
+ if (toolCall === null) return null;
return (
- // The sheet presents its own window, but the anchor itself must live in a SwiftUI Host.
-
- {
- if (!presented) onDismiss();
- }}
- >
-
+
+
- {toolCall ? (
-
-
-
-
-
- {toolCall.title}
-
-
-
- {metadata.length > 0 ? (
-
- {metadata.map((entry) => (
-
- {[entry.label ?? entry.key, entry.value].join(' ')}
-
- ))}
-
- ) : null}
- {contents.map((content, index) => {
- if (content.type === 'diff') {
- return (
-
- );
- }
- if (content.type === 'content' && content.content.type === 'text') {
- return (
-
- {content.content.text}
-
- );
- }
- return null;
- })}
- {command || rawOutput ? (
-
- {command ? (
- {command}
- ) : null}
- {rawOutput ? (
-
- {rawOutput}
-
- ) : null}
-
- ) : null}
- {failure ? (
-
- {t('failed')}: {failure}
+
+ {toolCall.title}
+
+ {metadata.length > 0 ? (
+
+ {metadata.map((entry) => (
+
+ {[entry.label ?? entry.key, entry.value].join(' ')}
+
+ ))}
+
+ ) : null}
+ {contents.map((content, index) => {
+ if (content.type === 'diff') {
+ return (
+
+ );
+ }
+ if (content.type === 'content' && content.content.type === 'text') {
+ return (
+
+ {content.content.text}
+
+ );
+ }
+ return null;
+ })}
+ {command || rawOutput ? (
+
+ {command ? (
+
+ {command}
+
+ ) : null}
+ {rawOutput ? (
+
+
+ {rawOutput}
- ) : null}
-
-
- ) : (
-
- )}
-
-
-
+
+ ) : null}
+
+ ) : null}
+ {failure ? (
+
+ {t('failed')}: {failure}
+
+ ) : null}
+
+
+
);
}
diff --git a/apps/mobile/src/components/conversation/transcript-record.ios.tsx b/apps/mobile/src/components/conversation/transcript-record.ios.tsx
new file mode 100644
index 000000000..dbb64aaef
--- /dev/null
+++ b/apps/mobile/src/components/conversation/transcript-record.ios.tsx
@@ -0,0 +1,42 @@
+import { Host, Text, VStack } from '@expo/ui/swift-ui';
+import { font, foregroundStyle, frame, textSelection } from '@expo/ui/swift-ui/modifiers';
+import type { TranscriptRecordProps } from './transcript-record.types';
+
+export function TranscriptRecord({
+ title,
+ entries,
+ error,
+}: TranscriptRecordProps): React.ReactNode {
+ return (
+
+
+
+ {title}
+
+ {entries.map((entry) => (
+
+
+ {entry.label}
+
+ {entry.value ? (
+
+ {entry.value}
+
+ ) : null}
+
+ ))}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/transcript-record.tsx b/apps/mobile/src/components/conversation/transcript-record.tsx
new file mode 100644
index 000000000..a428d6415
--- /dev/null
+++ b/apps/mobile/src/components/conversation/transcript-record.tsx
@@ -0,0 +1,34 @@
+import { Card, Column, Text } from '@expo/ui/jetpack-compose';
+import { fillMaxWidth, paddingAll } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import type { TranscriptRecordProps } from './transcript-record.types';
+
+export function TranscriptRecord({
+ title,
+ entries,
+ error,
+}: TranscriptRecordProps): React.ReactNode {
+ const colors = useAppMaterialColors();
+ return (
+
+
+
+ {title}
+ {entries.map((entry) => (
+
+ {entry.label}
+ {entry.value ? {entry.value} : null}
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/conversation/transcript-record.types.ts b/apps/mobile/src/components/conversation/transcript-record.types.ts
new file mode 100644
index 000000000..d26ee7be2
--- /dev/null
+++ b/apps/mobile/src/components/conversation/transcript-record.types.ts
@@ -0,0 +1,5 @@
+export interface TranscriptRecordProps {
+ title: string;
+ error?: boolean;
+ entries: Array<{ id: string; label: string; value?: string }>;
+}
diff --git a/apps/mobile/src/components/form/action-button.ios.tsx b/apps/mobile/src/components/form/action-button.ios.tsx
new file mode 100644
index 000000000..c36c17bba
--- /dev/null
+++ b/apps/mobile/src/components/form/action-button.ios.tsx
@@ -0,0 +1,47 @@
+import { Button, Host, Text } from '@expo/ui/swift-ui';
+import {
+ buttonStyle,
+ controlSize,
+ disabled as disabledModifier,
+ frame,
+} from '@expo/ui/swift-ui/modifiers';
+import type { ActionButtonProps } from './action-button.types';
+
+export function ActionButton({
+ label,
+ onPress,
+ disabled = false,
+ variant = 'primary',
+ fullWidth = false,
+}: ActionButtonProps): React.ReactNode {
+ return (
+
+
+
+ {label}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/action-button.tsx b/apps/mobile/src/components/form/action-button.tsx
new file mode 100644
index 000000000..6f1064027
--- /dev/null
+++ b/apps/mobile/src/components/form/action-button.tsx
@@ -0,0 +1,33 @@
+import { Button, FilledTonalButton, Text, TextButton } from '@expo/ui/jetpack-compose';
+import { defaultMinSize, fillMaxWidth } from '@expo/ui/jetpack-compose/modifiers';
+import type { ActionButtonProps } from './action-button.types';
+import { useAppMaterialColors } from './compose-theme.android';
+import { ThemedHost } from './themed-host.android';
+
+export function ActionButton({
+ label,
+ onPress,
+ disabled = false,
+ variant = 'primary',
+ fullWidth = false,
+}: ActionButtonProps): React.ReactNode {
+ const colors = useAppMaterialColors();
+ const Control =
+ variant === 'text' ? TextButton : variant === 'primary' ? Button : FilledTonalButton;
+ return (
+
+
+ {label}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/action-button.types.ts b/apps/mobile/src/components/form/action-button.types.ts
new file mode 100644
index 000000000..381eeeabe
--- /dev/null
+++ b/apps/mobile/src/components/form/action-button.types.ts
@@ -0,0 +1,7 @@
+export interface ActionButtonProps {
+ label: string;
+ onPress: () => void;
+ disabled?: boolean;
+ variant?: 'primary' | 'secondary' | 'text' | 'destructive';
+ fullWidth?: boolean;
+}
diff --git a/apps/mobile/src/components/form/compose-theme.android.ts b/apps/mobile/src/components/form/compose-theme.android.ts
new file mode 100644
index 000000000..ec56d6994
--- /dev/null
+++ b/apps/mobile/src/components/form/compose-theme.android.ts
@@ -0,0 +1,10 @@
+import type { MaterialColors } from '@expo/ui/jetpack-compose';
+import { useMaterialColors } from '@expo/ui/jetpack-compose';
+import { useResolvedColorScheme } from '@mobile/components/theme/use-color-scheme';
+
+/** `useMaterialColors` pinned to the APP theme: the bare hook follows the system scheme, which
+ * diverges the moment the in-app appearance preference overrides it. Android components must use
+ * this (and `ThemedHost`), never the bare forms. */
+export function useAppMaterialColors(): MaterialColors {
+ return useMaterialColors({ colorScheme: useResolvedColorScheme() });
+}
diff --git a/apps/mobile/src/components/form/icon-button.ios.tsx b/apps/mobile/src/components/form/icon-button.ios.tsx
new file mode 100644
index 000000000..4dd9f710f
--- /dev/null
+++ b/apps/mobile/src/components/form/icon-button.ios.tsx
@@ -0,0 +1,44 @@
+import { Button, Host } from '@expo/ui/swift-ui';
+import {
+ accessibilityLabel,
+ buttonStyle,
+ disabled,
+ frame,
+ labelStyle,
+} from '@expo/ui/swift-ui/modifiers';
+import type { SFSymbol } from 'sf-symbols-typescript';
+import type { NativeIconButtonProps } from './icon-button.types';
+
+const ICONS = {
+ send: 'arrow.up',
+ stop: 'stop.fill',
+ close: 'xmark',
+ previous: 'chevron.left',
+ next: 'chevron.right',
+ shield: 'shield',
+} as const satisfies Record;
+
+export function NativeIconButton({
+ icon,
+ label,
+ onPress,
+ disabled: isDisabled,
+ filled,
+}: NativeIconButtonProps): React.ReactNode {
+ return (
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/icon-button.tsx b/apps/mobile/src/components/form/icon-button.tsx
new file mode 100644
index 000000000..3620aaaa0
--- /dev/null
+++ b/apps/mobile/src/components/form/icon-button.tsx
@@ -0,0 +1,36 @@
+import { FilledIconButton, Icon, IconButton } from '@expo/ui/jetpack-compose';
+import { size } from '@expo/ui/jetpack-compose/modifiers';
+import arrowUp from '../../../assets/icons/arrow-up.xml';
+import previous from '../../../assets/icons/chevron-left.xml';
+import next from '../../../assets/icons/chevron-right.xml';
+import close from '../../../assets/icons/close.xml';
+import shield from '../../../assets/icons/shield.xml';
+import stop from '../../../assets/icons/stop.xml';
+import { useAppMaterialColors } from './compose-theme.android';
+import type { NativeIconButtonProps } from './icon-button.types';
+import { ThemedHost } from './themed-host.android';
+
+const ICONS = { send: arrowUp, stop, close, previous, next, shield };
+
+export function NativeIconButton({
+ icon,
+ label,
+ onPress,
+ disabled,
+ filled,
+}: NativeIconButtonProps): React.ReactNode {
+ const Control = filled ? FilledIconButton : IconButton;
+ const colors = useAppMaterialColors();
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/icon-button.types.ts b/apps/mobile/src/components/form/icon-button.types.ts
new file mode 100644
index 000000000..1bf24c257
--- /dev/null
+++ b/apps/mobile/src/components/form/icon-button.types.ts
@@ -0,0 +1,7 @@
+export interface NativeIconButtonProps {
+ icon: 'send' | 'stop' | 'close' | 'previous' | 'next' | 'shield';
+ label: string;
+ onPress: () => void;
+ disabled?: boolean;
+ filled?: boolean;
+}
diff --git a/apps/mobile/src/components/form/list.android.tsx b/apps/mobile/src/components/form/list.android.tsx
new file mode 100644
index 000000000..f21950619
--- /dev/null
+++ b/apps/mobile/src/components/form/list.android.tsx
@@ -0,0 +1,15 @@
+import { LazyColumn } from '@expo/ui/jetpack-compose';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import { useSafeAreaInsets } from 'react-native-safe-area-context';
+
+/** Android form scaffold — the Compose counterpart of the iOS `Host`+`Form` pair. The Host needs
+ * the viewport as its proposed size or the column collapses to its content, same trap as SwiftUI.
+ * LazyColumn maps each direct child to one lazy item, so keep children at section granularity. */
+export function FormList({ children }: React.PropsWithChildren): React.ReactNode {
+ const insets = useSafeAreaInsets();
+ return (
+
+ {children}
+
+ );
+}
diff --git a/apps/mobile/src/components/form/loading-view.android.tsx b/apps/mobile/src/components/form/loading-view.android.tsx
new file mode 100644
index 000000000..f00215ccb
--- /dev/null
+++ b/apps/mobile/src/components/form/loading-view.android.tsx
@@ -0,0 +1,16 @@
+import { Box, PullToRefreshBox } from '@expo/ui/jetpack-compose';
+import { fillMaxSize } from '@expo/ui/jetpack-compose/modifiers';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+
+/** Full-screen load state: the real pull-to-refresh box held in its refreshing state, so first
+ * load shows exactly the indicator a drag refresh shows, in the same place.
+ * topCenter: expo-ui's indicator slot drops Compose's align (see thread-list.tsx). */
+export function LoadingView(): React.ReactNode {
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/loading-view.ios.tsx b/apps/mobile/src/components/form/loading-view.ios.tsx
new file mode 100644
index 000000000..b253706f2
--- /dev/null
+++ b/apps/mobile/src/components/form/loading-view.ios.tsx
@@ -0,0 +1,30 @@
+import { ProgressView } from '@expo/ui/swift-ui';
+import { background, frame, padding, scaleEffect } from '@expo/ui/swift-ui/modifiers';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+
+/** Full-screen load state: the refresh spinner held where a drag refresh shows it — top-center
+ * just below the header — mirroring the Android `PullToRefreshBox` position. The scale matches
+ * the refresh control's spinner (larger than a bare ProgressView), and the grouped background
+ * matches the Form/List screens this state stands in for. */
+export function LoadingView(): React.ReactNode {
+ const palette = useNativePalette();
+ return (
+
+ );
+}
+
+/** In-section loading row: the same spinner centered in its form cell. */
+export function FormLoadingRow(): React.ReactNode {
+ return ;
+}
diff --git a/apps/mobile/src/components/form/navigation-row.ios.tsx b/apps/mobile/src/components/form/navigation-row.ios.tsx
new file mode 100644
index 000000000..4e3ea5cbb
--- /dev/null
+++ b/apps/mobile/src/components/form/navigation-row.ios.tsx
@@ -0,0 +1,45 @@
+import { HStack, Image, Spacer, Text, VStack } from '@expo/ui/swift-ui';
+import {
+ accessibilityAddTraits,
+ accessibilityElement,
+ badge,
+ contentShape,
+ onTapGesture,
+ shapes,
+} from '@expo/ui/swift-ui/modifiers';
+import type { NavigationRowProps } from '@mobile/components/form/navigation-row.types';
+import { FOOTNOTE, SECONDARY, TERTIARY } from '@mobile/components/form/styles.ios';
+
+// The stack only covers its text without this, so taps in the empty part of the row are lost.
+const WHOLE_ROW = contentShape(shapes.rectangle());
+
+/** A form row that pushes a route. SwiftUI draws the disclosure chevron from `NavigationLink`,
+ * which `@expo/ui` does not expose, so the row draws its own. A tap gesture rather than a
+ * `Button`, which is also what `NavigationLink` is: a Button swallows horizontal drags, and a
+ * row inside `SwipeActions` then opens the route instead of revealing its actions. */
+export function NavigationRow({
+ title,
+ subtitle,
+ badgeText,
+ onPress,
+}: NavigationRowProps): React.ReactNode {
+ return (
+
+
+ {title}
+ {subtitle ? {subtitle} : null}
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/navigation-row.tsx b/apps/mobile/src/components/form/navigation-row.tsx
index 616a9a67c..bf3a65553 100644
--- a/apps/mobile/src/components/form/navigation-row.tsx
+++ b/apps/mobile/src/components/form/navigation-row.tsx
@@ -1,34 +1,38 @@
-import { HStack, Image, Spacer, Text, VStack } from '@expo/ui/swift-ui';
-import { badge, contentShape, onTapGesture, shapes } from '@expo/ui/swift-ui/modifiers';
-import { FOOTNOTE, SECONDARY, TERTIARY } from '@mobile/components/form/styles';
+import { ListItem, Text } from '@expo/ui/jetpack-compose';
+import { clickable } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import type { NavigationRowProps } from '@mobile/components/form/navigation-row.types';
-// The stack only covers its text without this, so taps in the empty part of the row are lost.
-const WHOLE_ROW = contentShape(shapes.rectangle());
-
-/** A form row that pushes a route. SwiftUI draws the disclosure chevron from `NavigationLink`,
- * which `@expo/ui` does not expose, so the row draws its own. A tap gesture rather than a
- * `Button`, which is also what `NavigationLink` is: a Button swallows horizontal drags, and a
- * row inside `SwipeActions` then opens the route instead of revealing its actions. */
+/** Android form row that pushes a route: an MD3 `ListItem` whose whole-row ripple comes from
+ * `clickable`. MD3 list rows draw no disclosure chevron, so none is drawn. The SwiftUI variant
+ * lives in `navigation-row.ios.tsx`. */
export function NavigationRow({
title,
subtitle,
badgeText,
onPress,
-}: {
- title: string;
- subtitle?: string;
- /** Trailing status text, drawn the way a `List` row badge is — before the chevron. */
- badgeText?: string;
- onPress: () => void;
-}): React.ReactNode {
+}: NavigationRowProps): React.ReactNode {
+ const colors = useAppMaterialColors();
+
return (
-
-
+
+
{title}
- {subtitle ? {subtitle} : null}
-
-
-
-
+
+ {subtitle === undefined ? null : (
+
+
+ {subtitle}
+
+
+ )}
+ {badgeText === undefined ? null : (
+
+
+ {badgeText}
+
+
+ )}
+
);
}
diff --git a/apps/mobile/src/components/form/navigation-row.types.ts b/apps/mobile/src/components/form/navigation-row.types.ts
new file mode 100644
index 000000000..dc19cdd45
--- /dev/null
+++ b/apps/mobile/src/components/form/navigation-row.types.ts
@@ -0,0 +1,7 @@
+export interface NavigationRowProps {
+ title: string;
+ subtitle?: string;
+ /** Trailing status text, drawn the way a `List` row badge is. */
+ badgeText?: string;
+ onPress: () => void;
+}
diff --git a/apps/mobile/src/components/form/row-actions.android.tsx b/apps/mobile/src/components/form/row-actions.android.tsx
new file mode 100644
index 000000000..36b17af43
--- /dev/null
+++ b/apps/mobile/src/components/form/row-actions.android.tsx
@@ -0,0 +1,53 @@
+import { DropdownMenu, DropdownMenuItem, Text } from '@expo/ui/jetpack-compose';
+import { combinedClickable, fillMaxWidth } from '@expo/ui/jetpack-compose/modifiers';
+import { useState } from 'react';
+import { useAppMaterialColors } from './compose-theme.android';
+
+export function RowActions({
+ children,
+ onPress,
+ actions,
+}: {
+ children: React.ReactNode;
+ onPress?: () => void;
+ actions: Array<{
+ label: string;
+ onPress: () => void;
+ destructive?: boolean;
+ disabled?: boolean;
+ }>;
+}): React.ReactNode {
+ const [expanded, setExpanded] = useState(false);
+ const colors = useAppMaterialColors();
+ const openMenu = () => setExpanded(true);
+
+ return (
+ setExpanded(false)}
+ modifiers={[
+ fillMaxWidth(),
+ combinedClickable({ onClick: onPress ?? openMenu, onLongClick: openMenu }),
+ ]}
+ >
+ {children}
+
+ {actions.map((action) => (
+ {
+ setExpanded(false);
+ action.onPress();
+ }}
+ >
+
+ {action.label}
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/rows.android.tsx b/apps/mobile/src/components/form/rows.android.tsx
new file mode 100644
index 000000000..586faee66
--- /dev/null
+++ b/apps/mobile/src/components/form/rows.android.tsx
@@ -0,0 +1,67 @@
+import { ContainedLoadingIndicator, ListItem, Row, Switch, Text } from '@expo/ui/jetpack-compose';
+import { alpha, fillMaxWidth, padding, toggleable } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+
+/** Quiet in-section message row: empty states, hints, and errors. */
+export function FormHint({
+ tone = 'secondary',
+ children,
+}: {
+ tone?: 'secondary' | 'error';
+ children: string;
+}): React.ReactNode {
+ const colors = useAppMaterialColors();
+
+ return (
+
+ {children}
+
+ );
+}
+
+/** Centered in-section loading row, the `ProgressView` stand-in. Without `fillMaxWidth` the Row
+ * wraps its content and the "centered" indicator sits at the start edge. The indicator carries
+ * the refresh indicator's colors (`PullToRefreshDefaults`) so every load state reads as one. */
+export function FormLoadingRow(): React.ReactNode {
+ const colors = useAppMaterialColors();
+ return (
+
+
+
+ );
+}
+
+// Expo always installs the native Switch callback, so thumb taps do not reach the row handler.
+export function ToggleRow({
+ label,
+ value,
+ onValueChange,
+ enabled = true,
+}: {
+ label: string;
+ value: boolean;
+ onValueChange: (value: boolean) => void;
+ enabled?: boolean;
+}): React.ReactNode {
+ return (
+ onValueChange(!value), { role: 'switch' })] : []
+ }
+ >
+
+ {label}
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/section.android.tsx b/apps/mobile/src/components/form/section.android.tsx
new file mode 100644
index 000000000..37709653e
--- /dev/null
+++ b/apps/mobile/src/components/form/section.android.tsx
@@ -0,0 +1,45 @@
+import { Column, Row, Spacer, Text } from '@expo/ui/jetpack-compose';
+import { padding, weight } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+
+/** MD3 stand-in for a SwiftUI Form `Section`: a text subheader above the rows and quiet footer
+ * text below — the full-width list idiom, not iOS's inset-grouped chrome. `trailing` renders at
+ * the header's end; SwiftUI needed a hand-drawn header for that, a Row does it natively. */
+export function FormSection({
+ title,
+ trailing,
+ footer,
+ children,
+}: {
+ title?: string;
+ /** Header-trailing action, e.g. a `TextButton` refresh. */
+ trailing?: React.ReactNode;
+ footer?: string;
+ children: React.ReactNode;
+}): React.ReactNode {
+ const colors = useAppMaterialColors();
+
+ return (
+
+ {title === undefined && trailing === undefined ? null : (
+
+
+ {title ?? ''}
+
+
+ {trailing}
+
+ )}
+ {children}
+ {footer === undefined ? null : (
+
+ {footer}
+
+ )}
+
+ );
+}
diff --git a/apps/mobile/src/components/form/selector-sheet.android.tsx b/apps/mobile/src/components/form/selector-sheet.android.tsx
new file mode 100644
index 000000000..5c1e2fe94
--- /dev/null
+++ b/apps/mobile/src/components/form/selector-sheet.android.tsx
@@ -0,0 +1,160 @@
+import {
+ Column,
+ FilterChip,
+ FlowRow,
+ Icon,
+ ListItem,
+ ModalBottomSheet,
+ Text,
+} from '@expo/ui/jetpack-compose';
+import {
+ animateContentSize,
+ fillMaxWidth,
+ padding,
+ selectable,
+ selectableGroup,
+ verticalScroll,
+} from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import type {
+ SelectorChipAxis,
+ SelectorModelAxis,
+} from '@mobile/components/form/selector-sheet.types';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import checkGlyph from '../../../assets/icons/check.xml';
+
+function AxisTitle({ children }: { children: string }): React.ReactNode {
+ const colors = useAppMaterialColors();
+ return (
+
+ {children}
+
+ );
+}
+
+/** Single-select chip set for a compact axis (harness, effort). The M3 filter chip draws no
+ * checkmark of its own, so the selected chip supplies one as its leading icon. */
+function ChipAxisSection({
+ axis,
+ onPicked,
+}: {
+ axis: SelectorChipAxis;
+ /** Runs after a chip selection; the harness axis omits it to keep the sheet open. */
+ onPicked?: () => void;
+}): React.ReactNode {
+ return (
+
+ {axis.title}
+
+ {axis.options.map((option) => {
+ const selected = axis.selection === option.id;
+ return (
+ {
+ axis.onSelect(option.id);
+ onPicked?.();
+ }}
+ modifiers={[animateContentSize()]}
+ >
+ {selected ? (
+
+
+
+ ) : null}
+
+ {option.label}
+
+
+ );
+ })}
+
+
+ );
+}
+
+/** The harness/model/effort picker on Android — the MD3 shape of the web `ModelSelectorMenu`:
+ * one modal bottom sheet with chip sets for the compact axes and a grouped list for models.
+ * Picking a harness keeps the sheet open (it re-scopes the axes below); picking a model or an
+ * effort is terminal and closes, matching the other platforms. */
+export function SelectorSheet({
+ open,
+ onClose,
+ harness,
+ model,
+ effort,
+}: {
+ open: boolean;
+ onClose: () => void;
+ /** Draft-only harness chips; absent on a live session where the agent is fixed. */
+ harness?: SelectorChipAxis;
+ model?: SelectorModelAxis;
+ effort?: SelectorChipAxis;
+}): React.ReactNode {
+ const colors = useAppMaterialColors();
+
+ if (!open) return null;
+
+ return (
+
+
+
+ {harness === undefined ? null : }
+ {model === undefined ? null : (
+
+ {model.title}
+ {model.groups.map((group) => (
+
+ {group.label === null ? null : (
+
+ {group.label}
+
+ )}
+ {group.options.map((option) => (
+ {
+ model.onSelect(option.id);
+ onClose();
+ },
+ 'radioButton',
+ ),
+ ]}
+ >
+
+ {option.label}
+
+ {model.selection === option.id ? (
+
+
+
+ ) : null}
+
+ ))}
+
+ ))}
+
+ )}
+ {effort === undefined ? null : }
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/selector-sheet.types.ts b/apps/mobile/src/components/form/selector-sheet.types.ts
new file mode 100644
index 000000000..6349c34e9
--- /dev/null
+++ b/apps/mobile/src/components/form/selector-sheet.types.ts
@@ -0,0 +1,40 @@
+import type { ModelOption } from '@linkcode/ui/native';
+import { groupModelsByProvider, modelChoiceKey } from '@linkcode/ui/native';
+
+export interface SelectorChipAxis {
+ title: string;
+ /** Option id drawn selected, or null for none. */
+ selection: string | null;
+ options: Array<{ id: string; label: string }>;
+ onSelect: (id: string) => void;
+}
+
+export interface SelectorModelGroup {
+ /** Provider header above the rows, or null for headerless rows. */
+ label: string | null;
+ options: Array<{ id: string; label: string }>;
+}
+
+export interface SelectorModelAxis {
+ title: string;
+ selection: string | null;
+ groups: SelectorModelGroup[];
+ onSelect: (id: string) => void;
+}
+
+/** Model rows for the sheet: flat for a single-account list, provider-headed groups when the list
+ * spans accounts — the headers replace the per-row account subtitle a flat list would need. */
+export function modelAxisGroups(models: ModelOption[]): SelectorModelGroup[] {
+ const toOption = (model: ModelOption) => ({ id: modelChoiceKey(model), label: model.label });
+ const providerGroups = groupModelsByProvider(models);
+ if (providerGroups === null) return [{ label: null, options: models.map(toOption) }];
+ const groups: SelectorModelGroup[] = [];
+ if (providerGroups.ungrouped.length > 0) {
+ groups.push({ label: null, options: providerGroups.ungrouped.map(toOption) });
+ }
+ for (let i = 0, len = providerGroups.groups.length; i < len; i++) {
+ const group = providerGroups.groups[i];
+ groups.push({ label: group.label, options: group.options.map(toOption) });
+ }
+ return groups;
+}
diff --git a/apps/mobile/src/components/form/sheet-picker.android.tsx b/apps/mobile/src/components/form/sheet-picker.android.tsx
new file mode 100644
index 000000000..0f7f1ba75
--- /dev/null
+++ b/apps/mobile/src/components/form/sheet-picker.android.tsx
@@ -0,0 +1,120 @@
+import { Column, ListItem, ModalBottomSheet, RadioButton, Text } from '@expo/ui/jetpack-compose';
+import {
+ clickable,
+ padding,
+ selectable,
+ selectableGroup,
+ verticalScroll,
+} from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+
+export interface SheetPickerOption {
+ id: string;
+ label: string;
+ hint?: string;
+}
+
+export interface SheetPickerSection {
+ id: string;
+ title?: string;
+ /** Option id drawn as selected, or null for none. */
+ selection: string | null;
+ options: SheetPickerOption[];
+ onSelect: (id: string) => void;
+}
+
+export interface SheetPickerAction {
+ id: string;
+ label: string;
+ onPress: () => void;
+}
+
+const NO_ACTIONS: SheetPickerAction[] = [];
+
+/** MD3 bottom-sheet radio picker — the Android stand-in for every UIMenu-style selector (the
+ * Compose DropdownMenu has no submenus and no built-in trigger). Selecting closes the sheet;
+ * `actions` render below the sections as plain rows. */
+export function SheetPicker({
+ open,
+ onClose,
+ sections,
+ actions = NO_ACTIONS,
+}: {
+ open: boolean;
+ onClose: () => void;
+ sections: SheetPickerSection[];
+ actions?: SheetPickerAction[];
+}): React.ReactNode {
+ const colors = useAppMaterialColors();
+
+ if (!open) return null;
+
+ return (
+
+
+
+ {sections.map((section) => (
+
+ {section.title === undefined ? null : (
+
+ {section.title}
+
+ )}
+ {section.options.map((option) => (
+ {
+ section.onSelect(option.id);
+ onClose();
+ },
+ 'radioButton',
+ ),
+ ]}
+ >
+
+
+
+
+ {option.label}
+
+ {option.hint === undefined ? null : (
+
+
+ {option.hint}
+
+
+ )}
+
+ ))}
+
+ ))}
+ {actions.map((action) => (
+ {
+ action.onPress();
+ onClose();
+ }),
+ ]}
+ >
+
+ {action.label}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/form/styles.ts b/apps/mobile/src/components/form/styles.ios.ts
similarity index 100%
rename from apps/mobile/src/components/form/styles.ts
rename to apps/mobile/src/components/form/styles.ios.ts
diff --git a/apps/mobile/src/components/form/themed-host.android.tsx b/apps/mobile/src/components/form/themed-host.android.tsx
new file mode 100644
index 000000000..0a7d00f01
--- /dev/null
+++ b/apps/mobile/src/components/form/themed-host.android.tsx
@@ -0,0 +1,14 @@
+import { Host } from '@expo/ui/jetpack-compose';
+import { useResolvedColorScheme } from '@mobile/components/theme/use-color-scheme';
+
+/** `Host` pinned to the APP theme. A bare Host follows the system scheme, which diverges from
+ * the RN surfaces the moment the in-app appearance preference overrides it — dark text on a
+ * dark background. Android components must use this, never `Host` directly.
+ *
+ * Sizing to content needs `matchContents={{ vertical: true }}`, never bare `matchContents`: the
+ * horizontal axis then measures Compose unbounded, so `fillMaxWidth()` no-ops and a `weight(1)`
+ * child collapses to zero width (labels wrapping one character per line, text vanishing). */
+export function ThemedHost(props: React.ComponentProps): React.ReactNode {
+ const colorScheme = useResolvedColorScheme();
+ return ;
+}
diff --git a/apps/mobile/src/components/form/use-search-header-options.ios.ts b/apps/mobile/src/components/form/use-search-header-options.ios.ts
new file mode 100644
index 000000000..fd21d871c
--- /dev/null
+++ b/apps/mobile/src/components/form/use-search-header-options.ios.ts
@@ -0,0 +1,14 @@
+import type { NativeStackNavigationOptions } from 'expo-router';
+
+/** iOS threads search lives in the native search bar (`Stack.SearchBar`); there is no header
+ * search view to configure. */
+// eslint-disable-next-line @eslint-react/no-unnecessary-use-prefix -- platform stub; the name must mirror the Android hook
+export function useSearchHeaderOptions(_options: {
+ open: boolean;
+ placeholder: string;
+ closeLabel: string;
+ onQueryChange: (query: string) => void;
+ onClose: () => void;
+}): Partial {
+ return {};
+}
diff --git a/apps/mobile/src/components/form/use-search-header-options.tsx b/apps/mobile/src/components/form/use-search-header-options.tsx
new file mode 100644
index 000000000..dd4b2ff69
--- /dev/null
+++ b/apps/mobile/src/components/form/use-search-header-options.tsx
@@ -0,0 +1,69 @@
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import type { NativeStackNavigationOptions } from 'expo-router';
+import { ArrowLeftIcon } from 'lucide-react-native';
+import { Pressable, TextInput, View } from 'react-native';
+
+/** Header options that swap the top app bar's content for a full-width M3 search view while
+ * `open` — the classic Android search pattern. Spread LAST into the screen's options, after the
+ * usual leading/trailing header options. The search renders as the toolbar's center view (full
+ * width on Android) so the toolbar itself never unmounts: hiding the native header and mounting
+ * an in-content bar land on different frames and flicker. Every key is set in both states with
+ * explicit undefined where cleared: expo-router applies options via `setOptions`, a shallow
+ * merge, so an omitted key would keep its stale value. On iOS this is a no-op — search lives in
+ * the native search bar. */
+export function useSearchHeaderOptions({
+ open,
+ placeholder,
+ closeLabel,
+ onQueryChange,
+ onClose,
+}: {
+ open: boolean;
+ placeholder: string;
+ /** Accessibility name of the leading back arrow. */
+ closeLabel: string;
+ onQueryChange: (query: string) => void;
+ onClose: () => void;
+}): Partial {
+ const colors = useAppMaterialColors();
+ const palette = useNativePalette();
+
+ // Closed restores the navigator's Android header color rather than clearing: `setOptions`
+ // merges per key, so `undefined` would override the stack default down to the theme's white.
+ if (!open) {
+ return { headerTitle: undefined, headerStyle: { backgroundColor: palette.background } };
+ }
+ return {
+ headerLeft: undefined,
+ headerRight: undefined,
+ headerStyle: { backgroundColor: colors.surfaceContainerHigh },
+ headerTitle: () => (
+
+ {/* Pulls the arrow out of the toolbar's 16dp content inset to the nav-icon position. */}
+
+
+
+
+
+ ),
+ };
+}
diff --git a/apps/mobile/src/components/host/host-connection-state.ios.tsx b/apps/mobile/src/components/host/host-connection-state.ios.tsx
new file mode 100644
index 000000000..039443a94
--- /dev/null
+++ b/apps/mobile/src/components/host/host-connection-state.ios.tsx
@@ -0,0 +1,51 @@
+import { Button, Host, Image, Text, VStack } from '@expo/ui/swift-ui';
+import {
+ buttonStyle,
+ font,
+ multilineTextAlignment,
+ textSelection,
+} from '@expo/ui/swift-ui/modifiers';
+import { LoadingView } from '@mobile/components/form/loading-view.ios';
+import { FOOTNOTE, SECONDARY } from '@mobile/components/form/styles.ios';
+import type { HostConnectionStateProps } from '@mobile/components/host/host-connection-state.types';
+import { useTranslations } from 'use-intl';
+
+const CENTERED = multilineTextAlignment('center');
+const TITLE = font({ textStyle: 'title2', weight: 'semibold' });
+
+/** Full-screen fallback shown while a host connection is being established or has failed. */
+export function HostConnectionState({
+ status,
+ url,
+ failure,
+ onRetry,
+}: HostConnectionStateProps): React.ReactNode {
+ const t = useTranslations('mobile.connection');
+
+ return (
+
+ {status === 'connecting' ? (
+
+ ) : (
+
+
+
+ {t('unavailableTitle')}
+
+ {t('error', { url })}
+
+
+
+ {failure ? (
+ {failure}
+ ) : null}
+
+ )}
+
+ );
+}
diff --git a/apps/mobile/src/components/host/host-connection-state.tsx b/apps/mobile/src/components/host/host-connection-state.tsx
index be981c328..fdd71e588 100644
--- a/apps/mobile/src/components/host/host-connection-state.tsx
+++ b/apps/mobile/src/components/host/host-connection-state.tsx
@@ -1,25 +1,15 @@
-import { Button, Host, Image, ProgressView, Text, VStack } from '@expo/ui/swift-ui';
-import {
- buttonStyle,
- font,
- multilineTextAlignment,
- textSelection,
-} from '@expo/ui/swift-ui/modifiers';
-import { FOOTNOTE, SECONDARY } from '@mobile/components/form/styles';
+import { Button, Text as ComposeText } from '@expo/ui/jetpack-compose';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { LoadingView } from '@mobile/components/form/loading-view.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import type { HostConnectionStateProps } from '@mobile/components/host/host-connection-state.types';
+import { WifiOffIcon } from 'lucide-react-native';
+import { Text, View } from 'react-native';
import { useTranslations } from 'use-intl';
-const CENTERED = multilineTextAlignment('center');
-const TITLE = font({ textStyle: 'title2', weight: 'semibold' });
-
-export interface HostConnectionStateProps {
- status: 'connecting' | 'error';
- url: string;
- /** The underlying failure, when the controller reported one. */
- failure?: string;
- onRetry: () => void;
-}
-
-/** Full-screen fallback shown while a host connection is being established or has failed. */
+/** Android full-screen fallback while a host connection is being established or has failed.
+ * The text stays RN so the failure detail remains selectable (Compose text cannot offer that);
+ * colors come from the Material palette so it matches the Compose surfaces around it. */
export function HostConnectionState({
status,
url,
@@ -27,38 +17,35 @@ export function HostConnectionState({
onRetry,
}: HostConnectionStateProps): React.ReactNode {
const t = useTranslations('mobile.connection');
+ const colors = useAppMaterialColors();
+
+ if (status === 'connecting') return ;
return (
-
-
- {status === 'connecting' ? (
- <>
-
- {t('connecting')}
- >
- ) : (
- <>
-
-
- {t('unavailableTitle')}
-
- {t('error', { url })}
-
-
-
- {failure ? (
-
- {failure}
-
- ) : null}
- >
- )}
-
-
+
+
+
+
+ {t('unavailableTitle')}
+
+
+ {t('error', { url })}
+
+
+
+
+ {t('retry')}
+
+
+ {failure ? (
+
+ {failure}
+
+ ) : null}
+
);
}
diff --git a/apps/mobile/src/components/host/host-connection-state.types.ts b/apps/mobile/src/components/host/host-connection-state.types.ts
new file mode 100644
index 000000000..33e29aa99
--- /dev/null
+++ b/apps/mobile/src/components/host/host-connection-state.types.ts
@@ -0,0 +1,7 @@
+export interface HostConnectionStateProps {
+ status: 'connecting' | 'error';
+ url: string;
+ /** The underlying failure, when the controller reported one. */
+ failure?: string;
+ onRetry: () => void;
+}
diff --git a/apps/mobile/src/components/host/host-switcher-button.ios.ts b/apps/mobile/src/components/host/host-switcher-button.ios.ts
new file mode 100644
index 000000000..8202d567d
--- /dev/null
+++ b/apps/mobile/src/components/host/host-switcher-button.ios.ts
@@ -0,0 +1,5 @@
+/** iOS renders the host switcher as native bar items (`use-host-menu-items`); this twin exists
+ * only so the shared header hook can reference one component name on both platforms. */
+export function HostSwitcherButton(): React.ReactNode {
+ return null;
+}
diff --git a/apps/mobile/src/components/host/host-switcher-button.tsx b/apps/mobile/src/components/host/host-switcher-button.tsx
new file mode 100644
index 000000000..9117d1bd5
--- /dev/null
+++ b/apps/mobile/src/components/host/host-switcher-button.tsx
@@ -0,0 +1,67 @@
+import { SheetPicker } from '@mobile/components/form/sheet-picker.android';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { useHostRegistryStore, useSelectedHost } from '@mobile/stores/host-store';
+import { useRouter } from 'expo-router';
+import { ChevronsUpDownIcon } from 'lucide-react-native';
+import { useState } from 'react';
+import { Pressable, Text } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+/** Android host switcher: `unstable_headerLeftItems` is iOS-only, so the header carries a plain
+ * pressable that opens the host list as a bottom sheet. Switching writes the registry and stops —
+ * the host is not in the path, so whichever tab you are standing in stays put. */
+export function HostSwitcherButton(): React.ReactNode {
+ const t = useTranslations('mobile.connect');
+ const tSettings = useTranslations('mobile.settings');
+ const router = useRouter();
+ const hosts = useHostRegistryStore((state) => state.hosts);
+ const setLastActiveHostId = useHostRegistryStore((state) => state.setLastActiveHostId);
+ const selected = useSelectedHost();
+ const [open, setOpen] = useState(false);
+ const palette = useNativePalette();
+
+ if (!selected) return null;
+
+ return (
+ <>
+ setOpen(true)}
+ className="min-h-12 min-w-12 max-w-52 flex-row items-center gap-1 pr-3"
+ >
+
+ {selected.name}
+
+
+
+ setOpen(false)}
+ sections={[
+ {
+ id: 'hosts',
+ selection: selected.id,
+ options: hosts.map((host) => ({
+ id: host.id,
+ label: host.name,
+ hint: 'url' in host ? host.url : t('viaTunnel'),
+ })),
+ onSelect: setLastActiveHostId,
+ },
+ ]}
+ actions={[
+ {
+ id: 'manage',
+ label: tSettings('manageHosts'),
+ onPress: () => router.push('/connect'),
+ },
+ ]}
+ />
+ >
+ );
+}
diff --git a/apps/mobile/src/components/host/new-thread/draft-tools.ios.tsx b/apps/mobile/src/components/host/new-thread/draft-tools.ios.tsx
new file mode 100644
index 000000000..1c9ac4dd2
--- /dev/null
+++ b/apps/mobile/src/components/host/new-thread/draft-tools.ios.tsx
@@ -0,0 +1,127 @@
+import { Host, Menu, Picker, Text as UIText } from '@expo/ui/swift-ui';
+import { tag } from '@expo/ui/swift-ui/modifiers';
+import { AgentKindSchema } from '@linkcode/schema';
+import {
+ AGENT_LABELS,
+ AgentIcon,
+ groupModelsByProvider,
+ modelChoiceKey,
+} from '@linkcode/ui/native';
+import { OptionChip } from '@mobile/components/conversation/option-chip.ios';
+import type {
+ AgentSelectorChipProps,
+ ApprovalChipProps,
+} from '@mobile/components/host/new-thread/draft-tools.types';
+import { clearable, DEFAULT_TAG } from '@mobile/components/host/new-thread/draft-tools.types';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { View } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+/** The draft's approval-policy chip: a ghost shield opening the policy menu. */
+export function ApprovalChip({
+ policies,
+ policyId,
+ policyValue,
+ onPolicyIdChange,
+}: ApprovalChipProps): React.ReactNode {
+ const t = useTranslations('mobile.sessions');
+ if (policies.length === 0) return null;
+
+ return (
+
+
+
+ {t('defaultOption')}
+ {policies.map((policy) => (
+
+ {policy.name}
+
+ ))}
+
+
+
+ );
+}
+
+/** The draft's combined selector: the harness brand mark beside one menu with Agent / Model /
+ * Effort submenus, mirroring the desktop `ModelSelectorMenu`. The brand mark is an RN view and
+ * cannot enter the SwiftUI tree, so it sits flush beside the menu trigger — ghost styling makes
+ * the pair read as one control. */
+export function AgentSelectorChip({
+ kind,
+ onKindChange,
+ models,
+ modelKey,
+ onModelKeyChange,
+ selectorValue,
+ effortOptions,
+ effort,
+ onEffortChange,
+}: AgentSelectorChipProps): React.ReactNode {
+ const t = useTranslations('mobile.sessions');
+ const palette = useNativePalette();
+ // The account label joins a row only when the list spans several accounts — the same threshold
+ // as the web's provider grouping; a single-account list repeating its account is noise.
+ const spansAccounts = models !== null && groupModelsByProvider(models) !== null;
+
+ return (
+
+ {/* Footnote's 13pt metric, so the mark scales with the chip text beside it. */}
+
+
+ {/* Keyed by menu shape: children added to an already-created Menu never reach the
+ native UIMenu, so late-loading models/efforts must remount the chip. */}
+
+
+ {/* A labeled submenu, not an inline group: a bare Picker between sibling menus drops
+ out of the native UIMenu entirely. */}
+ {models !== null && models.length > 0 ? (
+
+ ) : null}
+ {effortOptions !== undefined && effortOptions.length > 0 ? (
+
+ ) : null}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/host/new-thread/draft-tools.tsx b/apps/mobile/src/components/host/new-thread/draft-tools.tsx
index 42e74c645..7f3af6a2c 100644
--- a/apps/mobile/src/components/host/new-thread/draft-tools.tsx
+++ b/apps/mobile/src/components/host/new-thread/draft-tools.tsx
@@ -1,64 +1,65 @@
-import { Host, Menu, Picker, Text as UIText } from '@expo/ui/swift-ui';
-import { tag } from '@expo/ui/swift-ui/modifiers';
-import type { AgentKind, ApprovalPolicy } from '@linkcode/schema';
import { AgentKindSchema } from '@linkcode/schema';
-import type { EffortOption, ModelOption } from '@linkcode/ui/native';
-import {
- AGENT_LABELS,
- AgentIcon,
- groupModelsByProvider,
- modelChoiceKey,
-} from '@linkcode/ui/native';
-import { OptionChip } from '@mobile/components/conversation/option-chip';
-import { useThemeColor } from 'heroui-native';
+import { AGENT_LABELS, AgentIcon } from '@linkcode/ui/native';
+import { ToolChip } from '@mobile/components/conversation/tool-chip.android';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { SelectorSheet } from '@mobile/components/form/selector-sheet.android';
+import type {
+ SelectorChipAxis,
+ SelectorModelAxis,
+} from '@mobile/components/form/selector-sheet.types';
+import { modelAxisGroups } from '@mobile/components/form/selector-sheet.types';
+import { SheetPicker } from '@mobile/components/form/sheet-picker.android';
+import type {
+ AgentSelectorChipProps,
+ ApprovalChipProps,
+} from '@mobile/components/host/new-thread/draft-tools.types';
+import { clearable, DEFAULT_TAG } from '@mobile/components/host/new-thread/draft-tools.types';
+import { useState } from 'react';
import { View } from 'react-native';
import { useTranslations } from 'use-intl';
-/** Tag for the leading "Default" entry in each menu — picking it clears the explicit pick, so
- * the agent's own startup resolution applies again. */
-const DEFAULT_TAG = '__default__';
-
-function clearable(onChange: (value: string | null) => void): (value: string) => void {
- return (value) => {
- onChange(value === DEFAULT_TAG ? null : value);
- };
-}
-
-/** The draft's approval-policy chip: a ghost shield opening the policy menu. */
+/** The draft's approval-policy chip on Android: a ghost shield opening the policy sheet. */
export function ApprovalChip({
policies,
policyId,
policyValue,
onPolicyIdChange,
-}: {
- policies: ApprovalPolicy[];
- policyId: string | null;
- policyValue: string;
- onPolicyIdChange: (policyId: string | null) => void;
-}): React.ReactNode {
+}: ApprovalChipProps): React.ReactNode {
const t = useTranslations('mobile.sessions');
+ const [open, setOpen] = useState(false);
if (policies.length === 0) return null;
return (
-
-
-
- {t('defaultOption')}
- {policies.map((policy) => (
-
- {policy.name}
-
- ))}
-
-
-
+ <>
+ setOpen(true)}
+ />
+ setOpen(false)}
+ sections={[
+ {
+ id: 'policy',
+ title: t('approvalLabel'),
+ selection: policyId ?? DEFAULT_TAG,
+ options: [
+ { id: DEFAULT_TAG, label: t('defaultOption') },
+ ...policies.map((policy) => ({ id: policy.policyId, label: policy.name })),
+ ],
+ onSelect: clearable(onPolicyIdChange),
+ },
+ ]}
+ />
+ >
);
}
-/** The draft's combined selector: the harness brand mark beside one menu with Agent / Model /
- * Effort submenus, mirroring the desktop `ModelSelectorMenu`. The brand mark is an RN view and
- * cannot enter the SwiftUI tree, so it sits flush beside the menu trigger — ghost styling makes
- * the pair read as one control. */
+/** The draft's combined selector on Android: the harness brand mark beside one chip opening the
+ * structured `SelectorSheet` — the same Agent / Model / Effort axes the iOS UIMenu nests as
+ * submenus. A "Default" entry leads the model and effort axes; picking it clears the pick. */
export function AgentSelectorChip({
kind,
onKindChange,
@@ -69,84 +70,65 @@ export function AgentSelectorChip({
effortOptions,
effort,
onEffortChange,
-}: {
- kind: AgentKind;
- onKindChange: (kind: AgentKind) => void;
- /** Account-backed options; null while loading (model submenu hidden). */
- models: ModelOption[] | null;
- /** `modelChoiceKey` of the explicit pick, or null for default. */
- modelKey: string | null;
- onModelKeyChange: (key: string | null) => void;
- /** Text on the chip — resolved model (and effort), or the harness name. */
- selectorValue: string;
- effortOptions: EffortOption[] | undefined;
- effort: string | null;
- onEffortChange: (effort: string | null) => void;
-}): React.ReactNode {
+}: AgentSelectorChipProps): React.ReactNode {
const t = useTranslations('mobile.sessions');
- const muted = useThemeColor('muted');
- // The account label joins a row only when the list spans several accounts — the same threshold
- // as the web's provider grouping; a single-account list repeating its account is noise.
- const spansAccounts = models !== null && groupModelsByProvider(models) !== null;
+ const colors = useAppMaterialColors();
+ const [open, setOpen] = useState(false);
+
+ const harnessAxis: SelectorChipAxis = {
+ title: t('kindLabel'),
+ selection: kind,
+ options: AgentKindSchema.options.map((option) => ({
+ id: option,
+ label: AGENT_LABELS[option],
+ })),
+ onSelect(id) {
+ const parsed = AgentKindSchema.safeParse(id);
+ if (parsed.success) onKindChange(parsed.data);
+ },
+ };
+ let modelAxis: SelectorModelAxis | undefined;
+ if (models !== null && models.length > 0) {
+ modelAxis = {
+ title: t('modelLabel'),
+ selection: modelKey ?? DEFAULT_TAG,
+ groups: [
+ { label: null, options: [{ id: DEFAULT_TAG, label: t('defaultOption') }] },
+ ...modelAxisGroups(models),
+ ],
+ onSelect: clearable(onModelKeyChange),
+ };
+ }
+ let effortAxis: SelectorChipAxis | undefined;
+ if (effortOptions !== undefined && effortOptions.length > 0) {
+ effortAxis = {
+ title: t('effortLabel'),
+ selection: effort ?? DEFAULT_TAG,
+ options: [
+ { id: DEFAULT_TAG, label: t('defaultOption') },
+ ...effortOptions.map((option) => ({ id: option.id, label: option.label })),
+ ],
+ onSelect: clearable(onEffortChange),
+ };
+ }
return (
{/* Footnote's 13pt metric, so the mark scales with the chip text beside it. */}
-
-
- {/* Keyed by menu shape: children added to an already-created Menu never reach the
- native UIMenu, so late-loading models/efforts must remount the chip. */}
-
-
- {/* A labeled submenu, not an inline group: a bare Picker between sibling menus drops
- out of the native UIMenu entirely. */}
- {models !== null && models.length > 0 ? (
-
- ) : null}
- {effortOptions !== undefined && effortOptions.length > 0 ? (
-
- ) : null}
-
-
+
+ setOpen(true)}
+ />
+ setOpen(false)}
+ harness={harnessAxis}
+ model={modelAxis}
+ effort={effortAxis}
+ />
);
}
diff --git a/apps/mobile/src/components/host/new-thread/draft-tools.types.ts b/apps/mobile/src/components/host/new-thread/draft-tools.types.ts
new file mode 100644
index 000000000..8f1d10b71
--- /dev/null
+++ b/apps/mobile/src/components/host/new-thread/draft-tools.types.ts
@@ -0,0 +1,34 @@
+import type { AgentKind, ApprovalPolicy } from '@linkcode/schema';
+import type { EffortOption, ModelOption } from '@linkcode/ui/native';
+
+/** Tag for the leading "Default" entry in each menu — picking it clears the explicit pick, so
+ * the agent's own startup resolution applies again. */
+export const DEFAULT_TAG = '__default__';
+
+export function clearable(onChange: (value: string | null) => void): (value: string) => void {
+ return (value) => {
+ onChange(value === DEFAULT_TAG ? null : value);
+ };
+}
+
+export interface ApprovalChipProps {
+ policies: ApprovalPolicy[];
+ policyId: string | null;
+ policyValue: string;
+ onPolicyIdChange: (policyId: string | null) => void;
+}
+
+export interface AgentSelectorChipProps {
+ kind: AgentKind;
+ onKindChange: (kind: AgentKind) => void;
+ /** Account-backed options; null while loading (model submenu hidden). */
+ models: ModelOption[] | null;
+ /** `modelChoiceKey` of the explicit pick, or null for default. */
+ modelKey: string | null;
+ onModelKeyChange: (key: string | null) => void;
+ /** Text on the chip — resolved model (and effort), or the harness name. */
+ selectorValue: string;
+ effortOptions: EffortOption[] | undefined;
+ effort: string | null;
+ onEffortChange: (effort: string | null) => void;
+}
diff --git a/apps/mobile/src/components/host/new-thread/new-thread-screen.tsx b/apps/mobile/src/components/host/new-thread/new-thread-screen.tsx
new file mode 100644
index 000000000..6baf28702
--- /dev/null
+++ b/apps/mobile/src/components/host/new-thread/new-thread-screen.tsx
@@ -0,0 +1,37 @@
+import { Composer } from '@mobile/components/conversation/composer';
+import { AgentSelectorChip, ApprovalChip } from '@mobile/components/host/new-thread/draft-tools';
+import { ProjectRow } from '@mobile/components/host/new-thread/project-row';
+import { useNewThreadDraft } from '@mobile/runtime/use-new-thread-draft';
+import { useRouter } from 'expo-router';
+import { noop } from 'foxact/noop';
+import { View } from 'react-native';
+import { KeyboardStickyView } from 'react-native-keyboard-controller';
+
+export function NewThreadScreen(): React.ReactNode {
+ const router = useRouter();
+ const { text, setText, start, creating, sendBlocked, error, project, approval, selector } =
+ useNewThreadDraft((sessionId) => router.replace(`/session/${sessionId}`));
+
+ return (
+ <>
+
+
+
+ {
+ void start(text);
+ }}
+ onStop={noop}
+ isRunning={false}
+ disabled={creating}
+ sendBlocked={sendBlocked}
+ error={error}
+ tools={}
+ trailing={}
+ />
+
+ >
+ );
+}
diff --git a/apps/mobile/src/components/host/new-thread/project-row.ios.tsx b/apps/mobile/src/components/host/new-thread/project-row.ios.tsx
new file mode 100644
index 000000000..871eb3882
--- /dev/null
+++ b/apps/mobile/src/components/host/new-thread/project-row.ios.tsx
@@ -0,0 +1,88 @@
+import { Host, HStack, Image, Menu, Picker, Text as UIText } from '@expo/ui/swift-ui';
+import {
+ accessibilityLabel,
+ font,
+ foregroundStyle,
+ padding,
+ tag,
+ tint,
+} from '@expo/ui/swift-ui/modifiers';
+import { repositoryLabel } from '@linkcode/ui/native';
+import type { ProjectRowProps } from '@mobile/components/host/new-thread/project-row.types';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { Color } from 'expo-router';
+import { FolderIcon } from 'lucide-react-native';
+import { TextInput, View } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+const ICON = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
+const VALUE = foregroundStyle({ type: 'hierarchical', style: 'primary' });
+const BODY = font({ textStyle: 'body' });
+/** SwiftUI draws a Menu label with the accent tint, and hierarchical styles derive from it —
+ * re-tinting to the label color turns the value primary and the icons secondary. */
+const LABEL_TINT = tint(Color.ios.label);
+
+/** The workspace picker as its own frame segment above the composer — outside the card, like the
+ * web composer's context bar: a bare row (icon + value + the up/down affordance) opening a native
+ * `UIMenu`. Falls back to a free path field while the host has no workspaces. */
+export function ProjectRow({
+ workspaces,
+ workspaceLabel,
+ cwd,
+ onCwdChange,
+ customPath,
+ onCustomPathChange,
+}: ProjectRowProps): React.ReactNode {
+ const t = useTranslations('mobile.sessions');
+ const palette = useNativePalette();
+
+ if (workspaces.length === 0) {
+ return (
+
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/host/new-thread/project-row.tsx b/apps/mobile/src/components/host/new-thread/project-row.tsx
index 45c6ad16f..7fd23d9f1 100644
--- a/apps/mobile/src/components/host/new-thread/project-row.tsx
+++ b/apps/mobile/src/components/host/new-thread/project-row.tsx
@@ -1,30 +1,14 @@
-import { Host, HStack, Image, Menu, Picker, Text as UIText } from '@expo/ui/swift-ui';
-import {
- accessibilityLabel,
- font,
- foregroundStyle,
- padding,
- tag,
- tint,
-} from '@expo/ui/swift-ui/modifiers';
-import type { WorkspaceRecord } from '@linkcode/schema';
import { repositoryLabel } from '@linkcode/ui/native';
-import { Color } from 'expo-router';
-import { useThemeColor } from 'heroui-native';
-import { FolderIcon } from 'lucide-react-native';
-import { TextInput, View } from 'react-native';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { SheetPicker } from '@mobile/components/form/sheet-picker.android';
+import type { ProjectRowProps } from '@mobile/components/host/new-thread/project-row.types';
+import { ChevronsUpDownIcon, FolderIcon } from 'lucide-react-native';
+import { useState } from 'react';
+import { Pressable, Text, TextInput, View } from 'react-native';
import { useTranslations } from 'use-intl';
-const ICON = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
-const VALUE = foregroundStyle({ type: 'hierarchical', style: 'primary' });
-const BODY = font({ textStyle: 'body' });
-/** SwiftUI draws a Menu label with the accent tint, and hierarchical styles derive from it —
- * re-tinting to the label color turns the value primary and the icons secondary. */
-const LABEL_TINT = tint(Color.ios.label);
-
-/** The workspace picker as its own frame segment above the composer — outside the card, like the
- * web composer's context bar: a bare row (icon + value + the up/down affordance) opening a native
- * `UIMenu`. Falls back to a free path field while the host has no workspaces. */
+/** Android workspace picker above the composer: a bare row opening the workspace list as a
+ * bottom sheet. Falls back to a free path field while the host has no workspaces. */
export function ProjectRow({
workspaces,
workspaceLabel,
@@ -32,27 +16,21 @@ export function ProjectRow({
onCwdChange,
customPath,
onCustomPathChange,
-}: {
- /** Already in recency order; the head is the default pick. */
- workspaces: WorkspaceRecord[];
- workspaceLabel: string;
- cwd: string | null;
- onCwdChange: (cwd: string) => void;
- customPath: string;
- onCustomPathChange: (path: string) => void;
-}): React.ReactNode {
+}: ProjectRowProps): React.ReactNode {
const t = useTranslations('mobile.sessions');
- const muted = useThemeColor('muted');
+ const colors = useAppMaterialColors();
+ const [open, setOpen] = useState(false);
if (workspaces.length === 0) {
return (
-
+
-
-
-
+ setOpen(true)}
+ className="flex-row items-center gap-2 py-1"
+ >
+
+
+ {workspaceLabel}
+
+
+
+ setOpen(false)}
+ sections={[
+ {
+ id: 'workspace',
+ title: t('projectLabel'),
+ selection: cwd,
+ options: workspaces.map((workspace) => ({
+ id: workspace.cwd,
+ label: workspace.name ?? repositoryLabel(workspace.cwd),
+ hint: workspace.cwd,
+ })),
+ onSelect: onCwdChange,
+ },
+ ]}
+ />
);
}
diff --git a/apps/mobile/src/components/host/new-thread/project-row.types.ts b/apps/mobile/src/components/host/new-thread/project-row.types.ts
new file mode 100644
index 000000000..14bdf6ad7
--- /dev/null
+++ b/apps/mobile/src/components/host/new-thread/project-row.types.ts
@@ -0,0 +1,11 @@
+import type { WorkspaceRecord } from '@linkcode/schema';
+
+export interface ProjectRowProps {
+ /** Already in recency order; the head is the default pick. */
+ workspaces: WorkspaceRecord[];
+ workspaceLabel: string;
+ cwd: string | null;
+ onCwdChange: (cwd: string) => void;
+ customPath: string;
+ onCustomPathChange: (path: string) => void;
+}
diff --git a/apps/mobile/src/components/host/thread-list/thread-list.ios.tsx b/apps/mobile/src/components/host/thread-list/thread-list.ios.tsx
new file mode 100644
index 000000000..807417b6a
--- /dev/null
+++ b/apps/mobile/src/components/host/thread-list/thread-list.ios.tsx
@@ -0,0 +1,41 @@
+import { List, Section } from '@expo/ui/swift-ui';
+import { listStyle, refreshable } from '@expo/ui/swift-ui/modifiers';
+import type { ThreadListProps } from './thread-list.types';
+import { ThreadRow } from './thread-row';
+import { useThreadListState } from './use-thread-list-state';
+
+/** The thread inbox body: one collapsible section per group. Grouping is decided by the
+ * caller — this only renders it.
+ *
+ * `sidebar` is not cosmetic — SwiftUI only honours a `Section`'s expanded state under that list
+ * style, so it is what makes the groups collapsible at all. */
+export function ThreadList({
+ groups,
+ labelFor,
+ onOpenThread,
+ onRefresh,
+}: ThreadListProps): React.ReactNode {
+ const { collapsed, setGroupExpanded, now } = useThreadListState();
+
+ return (
+
+ {groups.map((group) => (
+ setGroupExpanded(group.key, expanded)}
+ >
+ {group.sessions.map((session) => (
+ onOpenThread(session.sessionId)}
+ />
+ ))}
+
+ ))}
+
+ );
+}
diff --git a/apps/mobile/src/components/host/thread-list/thread-list.tsx b/apps/mobile/src/components/host/thread-list/thread-list.tsx
index d438a7ebc..f3f5ea29a 100644
--- a/apps/mobile/src/components/host/thread-list/thread-list.tsx
+++ b/apps/mobile/src/components/host/thread-list/thread-list.tsx
@@ -1,65 +1,84 @@
-import { List, Section } from '@expo/ui/swift-ui';
-import { listStyle, refreshable } from '@expo/ui/swift-ui/modifiers';
-import type { SessionInfo } from '@linkcode/schema';
-import type { ThreadGroup } from '@linkcode/ui/native';
-import { useEffect, useState } from 'react';
+import { Icon, LazyColumn, PullToRefreshBox, Row, Text } from '@expo/ui/jetpack-compose';
+import {
+ clickable,
+ defaultMinSize,
+ fillMaxWidth,
+ padding,
+ weight,
+} from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import { useState } from 'react';
+import expandLessGlyph from '../../../../assets/icons/expand-less.xml';
+import expandMoreGlyph from '../../../../assets/icons/expand-more.xml';
+import type { ThreadListProps } from './thread-list.types';
import { ThreadRow } from './thread-row';
+import { useThreadListState } from './use-thread-list-state';
-const MINUTE = 60000;
-
-/** The thread inbox body: one collapsible section per group. Grouping is decided by the
- * caller — this only renders it. Collapsed keys live here because the state is presentational:
- * nothing outside the list cares which groups are open.
- *
- * `sidebar` is not cosmetic — SwiftUI only honours a `Section`'s expanded state under that list
- * style, so it is what makes the groups collapsible at all. */
+// Each visible thread must be a direct LazyColumn child; grouping rows in a Column defeats laziness.
export function ThreadList({
groups,
labelFor,
onOpenThread,
onRefresh,
-}: {
- groups: ThreadGroup[];
- labelFor: (group: ThreadGroup) => string;
- onOpenThread: (sessionId: SessionInfo['sessionId']) => void;
- /** Drives SwiftUI's own pull-to-refresh, which holds its spinner until this resolves. */
- onRefresh: () => Promise;
-}): React.ReactNode {
- const [collapsed, setCollapsed] = useState>(() => new Set());
- const [now, setNow] = useState(Date.now);
-
- useEffect(() => {
- const timer = setInterval(() => setNow(Date.now()), MINUTE);
- return () => clearInterval(timer);
- }, []);
+}: ThreadListProps): React.ReactNode {
+ const { collapsed, setGroupExpanded, now } = useThreadListState();
+ const colors = useAppMaterialColors();
+ const [refreshing, setRefreshing] = useState(false);
- const setGroupExpanded = (key: string, expanded: boolean) =>
- setCollapsed((current) => {
- const next = new Set(current);
- if (expanded) next.delete(key);
- else next.add(key);
- return next;
- });
+ const refresh = () => {
+ setRefreshing(true);
+ void onRefresh().finally(() => setRefreshing(false));
+ };
return (
-
- {groups.map((group) => (
- setGroupExpanded(group.key, expanded)}
- >
- {group.sessions.map((session) => (
- onOpenThread(session.sessionId)}
- />
- ))}
-
- ))}
-
+
+ {/* expo-ui's indicator slot drops Compose's align(TopCenter); topCenter restores it, and the
+ * fillMaxWidth child is unaffected. */}
+
+
+ {groups.flatMap((group) => {
+ const expanded = !collapsed.has(group.key);
+ return [
+ setGroupExpanded(group.key, !expanded)),
+ fillMaxWidth(),
+ defaultMinSize({ minHeight: 48 }),
+ padding(16, 14, 16, 4),
+ ]}
+ >
+
+ {labelFor(group)}
+
+
+
,
+ ...(expanded
+ ? group.sessions.map((session) => (
+ onOpenThread(session.sessionId)}
+ />
+ ))
+ : []),
+ ];
+ })}
+
+
+
);
}
diff --git a/apps/mobile/src/components/host/thread-list/thread-list.types.ts b/apps/mobile/src/components/host/thread-list/thread-list.types.ts
new file mode 100644
index 000000000..eb759c928
--- /dev/null
+++ b/apps/mobile/src/components/host/thread-list/thread-list.types.ts
@@ -0,0 +1,10 @@
+import type { SessionInfo } from '@linkcode/schema';
+import type { ThreadGroup } from '@linkcode/ui/native';
+
+export interface ThreadListProps {
+ groups: ThreadGroup[];
+ labelFor: (group: ThreadGroup) => string;
+ onOpenThread: (sessionId: SessionInfo['sessionId']) => void;
+ /** Pull-to-refresh driver; the platform view holds its spinner until this resolves. */
+ onRefresh: () => Promise;
+}
diff --git a/apps/mobile/src/components/host/thread-list/thread-row.ios.tsx b/apps/mobile/src/components/host/thread-list/thread-row.ios.tsx
new file mode 100644
index 000000000..28d831b5c
--- /dev/null
+++ b/apps/mobile/src/components/host/thread-list/thread-row.ios.tsx
@@ -0,0 +1,71 @@
+import { HStack, Image, RNHostView, Spacer, Text, VStack } from '@expo/ui/swift-ui';
+import {
+ accessibilityAddTraits,
+ accessibilityElement,
+ contentShape,
+ foregroundStyle,
+ lineLimit,
+ onTapGesture,
+ shapes,
+} from '@expo/ui/swift-ui/modifiers';
+import type { SessionStatus } from '@linkcode/schema';
+import { AgentIcon } from '@linkcode/ui/native';
+import { FOOTNOTE, SECONDARY, TERTIARY } from '@mobile/components/form/styles.ios';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { formatRelativeShort } from '@mobile/utils/relative-time';
+import { threadTitle } from '@mobile/utils/thread-title';
+import { View } from 'react-native';
+import type { ThreadRowProps } from './thread-row.types';
+
+/** SwiftUI's semantic colours standing in for the `bg-*` tokens the RN dot used. */
+const STATUS_COLOR = {
+ starting: 'orange',
+ idle: 'gray',
+ running: 'green',
+ 'awaiting-input': 'orange',
+ stopped: 'secondary',
+} as const satisfies Record;
+
+const WHOLE_ROW = contentShape(shapes.rectangle());
+const GLYPH_SIZE = 18;
+
+/** One thread row: the harness brand mark (the web sidebar's ghost glyph), title, how long ago it
+ * moved, then a status dot and the chevron `NavigationLink` would have drawn.
+ *
+ * The brand mark is the RN `AgentIcon`, carried into the SwiftUI row through `RNHostView` —
+ * `matchContents` so the row takes the mark's Yoga-computed 18pt box, `pointerEvents="none"` so
+ * the hosted surface never swallows a tap meant for the row's own gesture. */
+export function ThreadRow({ session, now, onPress }: ThreadRowProps): React.ReactNode {
+ const palette = useNativePalette();
+
+ return (
+
+
+
+
+
+
+
+ {threadTitle(session)}
+
+ {formatRelativeShort(session.updatedAt, now)}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/host/thread-list/thread-row.tsx b/apps/mobile/src/components/host/thread-list/thread-row.tsx
index 521701e6a..21cce93ef 100644
--- a/apps/mobile/src/components/host/thread-list/thread-row.tsx
+++ b/apps/mobile/src/components/host/thread-list/thread-row.tsx
@@ -1,59 +1,68 @@
-import { HStack, Image, Spacer, Text, VStack } from '@expo/ui/swift-ui';
-import {
- contentShape,
- foregroundStyle,
- lineLimit,
- onTapGesture,
- shapes,
-} from '@expo/ui/swift-ui/modifiers';
-import type { SessionInfo, SessionStatus } from '@linkcode/schema';
-import { AGENT_LABELS, repositoryLabel } from '@linkcode/ui/native';
-import { FOOTNOTE, SECONDARY, TERTIARY } from '@mobile/components/form/styles';
+import { Box, Icon, ListItem, Text } from '@expo/ui/jetpack-compose';
+import { background, clickable, clip, Shapes, size } from '@expo/ui/jetpack-compose/modifiers';
+import type { AgentKind, SessionStatus } from '@linkcode/schema';
+import { AGENT_INITIALS } from '@linkcode/ui/native';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
import { formatRelativeShort } from '@mobile/utils/relative-time';
+import { threadTitle } from '@mobile/utils/thread-title';
+import agentClaudeCodeGlyph from '../../../../assets/icons/agent-claude-code.xml';
+import agentCodexGlyph from '../../../../assets/icons/agent-codex.xml';
+import agentOpencodeGlyph from '../../../../assets/icons/agent-opencode.xml';
+import type { ThreadRowProps } from './thread-row.types';
-/** SwiftUI's semantic colours standing in for the `bg-*` tokens the RN dot used. */
-const STATUS_COLOR = {
- starting: 'orange',
- idle: 'gray',
- running: 'green',
- 'awaiting-input': 'orange',
- stopped: 'secondary',
-} as const satisfies Record;
+/** Brand marks vendored as vector drawables from `@proj-airi/lobe-icons` (the web glyph set);
+ * kinds without a lobe glyph fall back to initials, matching the web sidebar. */
+const AGENT_GLYPHS: Partial> = {
+ 'claude-code': agentClaudeCodeGlyph,
+ codex: agentCodexGlyph,
+ opencode: agentOpencodeGlyph,
+};
-const WHOLE_ROW = contentShape(shapes.rectangle());
+const GLYPH_SIZE = 18;
-/** One thread row: title (desktop-matching fallback), which agent is driving it and how long ago it
- * moved, then a status dot and the chevron `NavigationLink` would have drawn.
- *
- * The subtitle names the agent rather than the project because the list is already grouped by
- * project — repeating it there would spend the line on something the section header already says.
- * The agent is text, not a glyph: the brand marks are RN SVG components and cannot cross into
- * SwiftUI. */
-export function ThreadRow({
- session,
- now,
- onPress,
-}: {
- session: SessionInfo;
- now: number;
- onPress: () => void;
-}): React.ReactNode {
- const title = session.title ?? `${AGENT_LABELS[session.kind]} in ${repositoryLabel(session.cwd)}`;
- const subtitle = `${AGENT_LABELS[session.kind]} · ${formatRelativeShort(session.updatedAt, now)}`;
+/** Android thread row: MD3 ListItem with the harness brand mark leading (the web sidebar's ghost
+ * glyph), the status dot drawn as a clipped Box, and no disclosure chevron, per MD3. The dot
+ * speaks Material roles — MD3 has no success/warning — running reads as the active accent,
+ * waiting states as tertiary. */
+export function ThreadRow({ session, now, onPress }: ThreadRowProps): React.ReactNode {
+ const colors = useAppMaterialColors();
+ const statusColor = {
+ starting: colors.tertiary,
+ idle: colors.outline,
+ running: colors.primary,
+ 'awaiting-input': colors.tertiary,
+ stopped: colors.outline,
+ } satisfies Record;
+ const glyph = AGENT_GLYPHS[session.kind];
return (
-
-
- {title}
- {subtitle}
-
-
-
-
-
+
+
+ {glyph === undefined ? (
+
+
+ {AGENT_INITIALS[session.kind]}
+
+
+ ) : (
+
+ )}
+
+
+
+ {threadTitle(session)}
+
+
+
+
+ {formatRelativeShort(session.updatedAt, now)}
+
+
+
+
+
+
);
}
diff --git a/apps/mobile/src/components/host/thread-list/thread-row.types.ts b/apps/mobile/src/components/host/thread-list/thread-row.types.ts
new file mode 100644
index 000000000..09ce6fd2a
--- /dev/null
+++ b/apps/mobile/src/components/host/thread-list/thread-row.types.ts
@@ -0,0 +1,8 @@
+import type { SessionInfo } from '@linkcode/schema';
+
+export interface ThreadRowProps {
+ session: SessionInfo;
+ /** Clock for the relative timestamp, ticked by the list so rows re-render together. */
+ now: number;
+ onPress: () => void;
+}
diff --git a/apps/mobile/src/components/host/thread-list/use-thread-list-state.ts b/apps/mobile/src/components/host/thread-list/use-thread-list-state.ts
new file mode 100644
index 000000000..f9115f17c
--- /dev/null
+++ b/apps/mobile/src/components/host/thread-list/use-thread-list-state.ts
@@ -0,0 +1,29 @@
+import { useEffect, useState } from 'react';
+
+const MINUTE = 60000;
+
+/** Presentational state both platform lists share: which groups are collapsed (nothing outside
+ * the list cares) and a once-a-minute clock for the relative timestamps. */
+export function useThreadListState(): {
+ collapsed: ReadonlySet;
+ setGroupExpanded: (key: string, expanded: boolean) => void;
+ now: number;
+} {
+ const [collapsed, setCollapsed] = useState>(() => new Set());
+ const [now, setNow] = useState(Date.now);
+
+ useEffect(() => {
+ const timer = setInterval(() => setNow(Date.now()), MINUTE);
+ return () => clearInterval(timer);
+ }, []);
+
+ const setGroupExpanded = (key: string, expanded: boolean) =>
+ setCollapsed((current) => {
+ const next = new Set(current);
+ if (expanded) next.delete(key);
+ else next.add(key);
+ return next;
+ });
+
+ return { collapsed, setGroupExpanded, now };
+}
diff --git a/apps/mobile/src/components/host/threads-screen.ios.tsx b/apps/mobile/src/components/host/threads-screen.ios.tsx
new file mode 100644
index 000000000..dbd7016f4
--- /dev/null
+++ b/apps/mobile/src/components/host/threads-screen.ios.tsx
@@ -0,0 +1,80 @@
+import { Form, Host, Section, Button as UIButton, Text as UIText } from '@expo/ui/swift-ui';
+import { LoadingView } from '@mobile/components/form/loading-view.ios';
+import { SECONDARY } from '@mobile/components/form/styles.ios';
+import { ThreadList } from '@mobile/components/host/thread-list/thread-list';
+import { useThreadInbox } from '@mobile/runtime/use-thread-inbox';
+import { Stack, useRouter } from 'expo-router';
+import { noop } from 'foxact/noop';
+import { useCallback } from 'react';
+import { useTranslations } from 'use-intl';
+
+/** Taken from the search bar itself: RN's own replacement for the event it declares carries no text. */
+type SearchBarChangeEvent = Parameters<
+ NonNullable['onChangeText']>
+>[0];
+
+/** Threads inbox body: grouped sessions under collapsible headers, with the native search bar
+ * stacked below the navigation bar. The open/close props are the Android toggle; iOS search is
+ * always available in the header and writes back into the route-owned query. */
+export function ThreadsScreen({
+ query = '',
+ onQueryChange = noop,
+}: {
+ searchOpen?: boolean;
+ onCloseSearch?: () => void;
+ query?: string;
+ onQueryChange?: (query: string) => void;
+}): React.ReactNode {
+ const t = useTranslations('mobile.sessions');
+ const router = useRouter();
+ const { loading, groups, hasQuery, groupLabel, onRefresh } = useThreadInbox(query);
+
+ // Stable so the search bar's options object survives a keystroke without re-registering.
+ const onSearchChange = useCallback(
+ (event: SearchBarChangeEvent) => onQueryChange(event.nativeEvent.text),
+ [onQueryChange],
+ );
+
+ return (
+ <>
+ {/* `stacked` keeps the field below the inline title instead of moving into the iOS 26 toolbar. */}
+
+ {/* The list needs the viewport as its proposed size, otherwise SwiftUI collapses it. */}
+
+ {loading ? (
+
+ ) : groups.length === 0 ? (
+ // A query that matched nothing is not an empty inbox: saying "no threads yet" there
+ // reads as though the existing threads were lost, and offering to start one is no
+ // remedy for a bad search.
+
+ ) : (
+ router.push(`/session/${sessionId}`)}
+ onRefresh={onRefresh}
+ />
+ )}
+
+ >
+ );
+}
diff --git a/apps/mobile/src/components/host/threads-screen.tsx b/apps/mobile/src/components/host/threads-screen.tsx
new file mode 100644
index 000000000..e285a072a
--- /dev/null
+++ b/apps/mobile/src/components/host/threads-screen.tsx
@@ -0,0 +1,75 @@
+import { Button, Text } from '@expo/ui/jetpack-compose';
+import { padding } from '@expo/ui/jetpack-compose/modifiers';
+import { FormList } from '@mobile/components/form/list.android';
+import { LoadingView } from '@mobile/components/form/loading-view.android';
+import { FormHint } from '@mobile/components/form/rows.android';
+import { FormSection } from '@mobile/components/form/section.android';
+import { ThreadList } from '@mobile/components/host/thread-list/thread-list';
+import { useThreadInbox } from '@mobile/runtime/use-thread-inbox';
+import { useRouter } from 'expo-router';
+import { noop } from 'foxact/noop';
+import { useEffect } from 'react';
+import { BackHandler } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+/** Android threads inbox body. The search field lives in the route's header (the M3 search view
+ * swaps the top app bar's content via `useSearchHeaderOptions`); this screen only filters by the
+ * route-owned query and collapses the search on hardware back. */
+export function ThreadsScreen({
+ searchOpen = false,
+ onCloseSearch = noop,
+ query = '',
+}: {
+ searchOpen?: boolean;
+ onCloseSearch?: () => void;
+ query?: string;
+ onQueryChange?: (query: string) => void;
+}): React.ReactNode {
+ const t = useTranslations('mobile.sessions');
+ const router = useRouter();
+ const { loading, groups, hasQuery, groupLabel, onRefresh } = useThreadInbox(query);
+
+ // The hardware back gesture collapses the search first, as every Android search view does.
+ useEffect(() => {
+ if (!searchOpen) return;
+ const subscription = BackHandler.addEventListener('hardwareBackPress', () => {
+ onCloseSearch();
+ return true;
+ });
+ return () => subscription.remove();
+ }, [searchOpen, onCloseSearch]);
+
+ return (
+ <>
+ {loading ? (
+
+ ) : groups.length === 0 ? (
+ // A query that matched nothing is not an empty inbox: saying "no threads yet" there
+ // reads as though the existing threads were lost, and offering to start one is no
+ // remedy for a bad search.
+
+ {hasQuery ? (
+ {t('searchEmpty')}
+ ) : (
+
+ {t('emptyTitle')}
+ router.push('/new-thread')}
+ modifiers={[padding(16, 4, 16, 4)]}
+ >
+ {t('newThread')}
+
+
+ )}
+
+ ) : (
+ router.push(`/session/${sessionId}`)}
+ onRefresh={onRefresh}
+ />
+ )}
+ >
+ );
+}
diff --git a/apps/mobile/src/components/host/use-host-header-options.tsx b/apps/mobile/src/components/host/use-host-header-options.tsx
new file mode 100644
index 000000000..34bcaf254
--- /dev/null
+++ b/apps/mobile/src/components/host/use-host-header-options.tsx
@@ -0,0 +1,16 @@
+import { HostSwitcherButton } from '@mobile/components/host/host-switcher-button';
+import { useHostMenuItems } from '@mobile/components/host/use-host-menu-items';
+import type { NativeStackNavigationOptions } from 'expo-router';
+import { Platform } from 'react-native';
+
+/** The header's host switcher as stack-screen options: native bar items on iOS, a headerLeft
+ * pressable opening a bottom sheet on Android — `unstable_headerLeftItems` is silently ignored
+ * there. Spread into each tab's `Stack.Screen` options. */
+export function useHostHeaderOptions(): Partial {
+ const hostMenuItems = useHostMenuItems();
+
+ if (Platform.OS === 'ios') {
+ return { unstable_headerLeftItems: () => hostMenuItems };
+ }
+ return { headerLeft: () => };
+}
diff --git a/apps/mobile/src/components/settings/settings-screen.ios.tsx b/apps/mobile/src/components/settings/settings-screen.ios.tsx
new file mode 100644
index 000000000..2d7c572bd
--- /dev/null
+++ b/apps/mobile/src/components/settings/settings-screen.ios.tsx
@@ -0,0 +1,151 @@
+import { Form, Host, Link, Picker, Section, Text, Toggle, VStack } from '@expo/ui/swift-ui';
+import { disabled, font, foregroundStyle, pickerStyle, tag } from '@expo/ui/swift-ui/modifiers';
+import { AgentKindSchema, WIRE_PROTOCOL_VERSION } from '@linkcode/schema';
+import { NavigationRow } from '@mobile/components/form/navigation-row';
+import {
+ PRIVACY_POLICY_URL,
+ SUPPORT_URL,
+ TERMS_OF_SERVICE_URL,
+ THEME_LABEL_KEY,
+ THEME_PREFERENCES,
+} from '@mobile/components/settings/settings-screen.shared';
+import { LARGE_TITLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
+import { useCloudAccount } from '@mobile/runtime/cloud/account';
+import { setMobileProductAnalyticsEnabled } from '@mobile/runtime/product-analytics';
+import { useNotificationSettings } from '@mobile/runtime/use-notification-settings';
+import { useAnalyticsPreferenceStore } from '@mobile/stores/analytics-store';
+import { useSettingsStore } from '@mobile/stores/settings-store';
+import { Stack, useRouter } from 'expo-router';
+import { View } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
+
+/** App settings: account + host management entries plus the About/contract summary. Nothing here is
+ * host-scoped, and the screen is deliberately ungated — this is where "Manage hosts" lives, so it
+ * has to survive the selected host being unreachable. */
+export function SettingsScreen(): React.ReactNode {
+ const t = useTranslations('mobile.settings');
+ const tAbout = useTranslations('mobile.about');
+ const router = useRouter();
+ const account = useCloudAccount();
+ const productAnalyticsEnabled = useAnalyticsPreferenceStore((state) => state.enabled);
+ const themePreference = useSettingsStore((state) => state.themePreference);
+ const {
+ enabled: notificationsEnabled,
+ canUpdate: notificationsToggleEnabled,
+ update: updateNotifications,
+ } = useNotificationSettings();
+ const setThemePreference = useSettingsStore((state) => state.setThemePreference);
+ const keepHostsConnected = useSettingsStore((state) => state.keepHostsConnected);
+ const setKeepHostsConnected = useSettingsStore((state) => state.setKeepHostsConnected);
+
+ // The flex container is load-bearing: a SwiftUI host left as the screen's direct child is
+ // proposed the whole window and paints straight over the large title.
+ return (
+
+
+ {/* Form needs the viewport as its proposed size, otherwise it collapses to its content. */}
+
+ }>
+
+
+
+
+
+ {THEME_PREFERENCES.map((preference) => (
+
+ {t(THEME_LABEL_KEY[preference])}
+
+ ))}
+
+
+
+ }>
+
+
+
+
+ {account.status === 'signed-in'
+ ? t('notificationsHint')
+ : t('notificationsRequiresCloud')}
+
+ }
+ >
+
+
+
+ {/* Native links open the URL themselves — no Linking.openURL fallback to get wrong. */}
+
+
+
+
+ {tAbout('title')}
+
+ {tAbout('contract', { version: WIRE_PROTOCOL_VERSION })}
+
+
+
+
+
+ }
+ >
+ {AgentKindSchema.options.map((kind) => (
+ {kind}
+ ))}
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/settings/settings-screen.shared.ts b/apps/mobile/src/components/settings/settings-screen.shared.ts
new file mode 100644
index 000000000..75abcc828
--- /dev/null
+++ b/apps/mobile/src/components/settings/settings-screen.shared.ts
@@ -0,0 +1,13 @@
+import type { ThemePreference } from '@mobile/stores/settings-store';
+
+export const THEME_PREFERENCES: readonly ThemePreference[] = ['system', 'light', 'dark'];
+
+export const THEME_LABEL_KEY = {
+ system: 'appearanceSystem',
+ light: 'appearanceLight',
+ dark: 'appearanceDark',
+} as const;
+
+export const PRIVACY_POLICY_URL = 'https://linkcode.ai/privacy';
+export const TERMS_OF_SERVICE_URL = 'https://linkcode.ai/terms';
+export const SUPPORT_URL = 'https://linkcode.ai/support';
diff --git a/apps/mobile/src/components/settings/settings-screen.tsx b/apps/mobile/src/components/settings/settings-screen.tsx
index e3d0b7816..69b66a7bc 100644
--- a/apps/mobile/src/components/settings/settings-screen.tsx
+++ b/apps/mobile/src/components/settings/settings-screen.tsx
@@ -1,39 +1,41 @@
-import { Form, Host, Link, Picker, Section, Text, Toggle, VStack } from '@expo/ui/swift-ui';
-import { disabled, font, foregroundStyle, pickerStyle, tag } from '@expo/ui/swift-ui/modifiers';
+import {
+ Column,
+ SegmentedButton,
+ SingleChoiceSegmentedButtonRow,
+ Text,
+} from '@expo/ui/jetpack-compose';
+import { fillMaxWidth, padding } from '@expo/ui/jetpack-compose/modifiers';
import { AgentKindSchema, WIRE_PROTOCOL_VERSION } from '@linkcode/schema';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { FormList } from '@mobile/components/form/list.android';
import { NavigationRow } from '@mobile/components/form/navigation-row';
+import { ToggleRow } from '@mobile/components/form/rows.android';
+import { FormSection } from '@mobile/components/form/section.android';
+import {
+ PRIVACY_POLICY_URL,
+ SUPPORT_URL,
+ TERMS_OF_SERVICE_URL,
+ THEME_LABEL_KEY,
+ THEME_PREFERENCES,
+} from '@mobile/components/settings/settings-screen.shared';
import { LARGE_TITLE_HEADER_OPTIONS } from '@mobile/components/shell/use-stack-screen-options';
import { useCloudAccount } from '@mobile/runtime/cloud/account';
-import {
- disableDeviceNotifications,
- enableDeviceNotifications,
-} from '@mobile/runtime/notifications';
import { setMobileProductAnalyticsEnabled } from '@mobile/runtime/product-analytics';
+import { useNotificationSettings } from '@mobile/runtime/use-notification-settings';
import { useAnalyticsPreferenceStore } from '@mobile/stores/analytics-store';
-import type { ThemePreference } from '@mobile/stores/settings-store';
import { useSettingsStore } from '@mobile/stores/settings-store';
import { Stack, useRouter } from 'expo-router';
-import { useRef, useState } from 'react';
-import { Alert, Linking, View } from 'react-native';
+import { noop } from 'foxact/noop';
+import { Linking, View } from 'react-native';
import { useTranslations } from 'use-intl';
-const THEME_PREFERENCES: readonly ThemePreference[] = ['system', 'light', 'dark'];
-
-const THEME_LABEL_KEY = {
- system: 'appearanceSystem',
- light: 'appearanceLight',
- dark: 'appearanceDark',
-} as const;
-
-const PRIVACY_POLICY_URL = 'https://linkcode.ai/privacy';
-const TERMS_OF_SERVICE_URL = 'https://linkcode.ai/terms';
-const SUPPORT_URL = 'https://linkcode.ai/support';
-
-const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
+/** Compose has no `Link` row, so legal rows open the URL through RN `Linking`. */
+function openUrl(url: string): void {
+ Linking.openURL(url).catch(noop);
+}
-/** App settings: account + host management entries plus the About/contract summary. Nothing here is
- * host-scoped, and the screen is deliberately ungated — this is where "Manage hosts" lives, so it
- * has to survive the selected host being unreachable. */
+/** Android settings, mirroring the SwiftUI screen in `settings-screen.ios.tsx`: same sections,
+ * MD3 dress. */
export function SettingsScreen(): React.ReactNode {
const t = useTranslations('mobile.settings');
const tAbout = useTranslations('mobile.about');
@@ -41,42 +43,15 @@ export function SettingsScreen(): React.ReactNode {
const account = useCloudAccount();
const productAnalyticsEnabled = useAnalyticsPreferenceStore((state) => state.enabled);
const themePreference = useSettingsStore((state) => state.themePreference);
- const notificationsEnabled = useSettingsStore((state) => state.notificationsEnabled);
+ const {
+ enabled: notificationsEnabled,
+ canUpdate: notificationsToggleEnabled,
+ update: updateNotifications,
+ } = useNotificationSettings();
const setThemePreference = useSettingsStore((state) => state.setThemePreference);
const keepHostsConnected = useSettingsStore((state) => state.keepHostsConnected);
const setKeepHostsConnected = useSettingsStore((state) => state.setKeepHostsConnected);
- const [notificationUpdatePending, setNotificationUpdatePending] = useState(false);
- const notificationUpdatePendingRef = useRef(false);
- const updateNotifications = async (enabled: boolean) => {
- if (account.status !== 'signed-in' || notificationUpdatePendingRef.current) return;
- notificationUpdatePendingRef.current = true;
- setNotificationUpdatePending(true);
- try {
- if (!enabled) {
- await disableDeviceNotifications();
- return;
- }
- if (await enableDeviceNotifications(account.user.id)) return;
- Alert.alert(t('notificationsDeniedTitle'), t('notificationsDenied'), [
- { text: t('cancel'), style: 'cancel' },
- {
- text: t('openSettings'),
- onPress() {
- void Linking.openSettings();
- },
- },
- ]);
- } catch {
- Alert.alert(t('notificationsErrorTitle'), t('notificationsError'));
- } finally {
- notificationUpdatePendingRef.current = false;
- setNotificationUpdatePending(false);
- }
- };
-
- // The flex container is load-bearing: a SwiftUI host left as the screen's direct child is
- // proposed the whole window and paints straight over the large title.
return (
- {/* Form needs the viewport as its proposed size, otherwise it collapses to its content. */}
-
-
-
+
+ {AgentKindSchema.options.map((kind) => (
+
+ {kind}
+
+ ))}
+
+
);
}
+
+function AboutBlock({ title, contract }: { title: string; contract: string }): React.ReactNode {
+ const colors = useAppMaterialColors();
+
+ return (
+
+ {title}
+
+ {contract}
+
+
+ );
+}
diff --git a/apps/mobile/src/components/settings/terminal-appearance-screen.ios.tsx b/apps/mobile/src/components/settings/terminal-appearance-screen.ios.tsx
new file mode 100644
index 000000000..9143c3914
--- /dev/null
+++ b/apps/mobile/src/components/settings/terminal-appearance-screen.ios.tsx
@@ -0,0 +1,113 @@
+import {
+ Circle,
+ Form,
+ Host,
+ HStack,
+ Picker,
+ Section,
+ Text,
+ VStack,
+ ZStack,
+} from '@expo/ui/swift-ui';
+import {
+ font,
+ foregroundStyle,
+ frame,
+ pickerStyle,
+ strokeBorder,
+ tag,
+} from '@expo/ui/swift-ui/modifiers';
+import {
+ resolveTerminalTheme,
+ TERMINAL_COLOR_SCHEMES,
+ TERMINAL_FONT_SIZES,
+ useTerminalPrefsStore,
+} from '@mobile/stores/terminal-prefs-store';
+import { useTranslations } from 'use-intl';
+
+const SWATCH = 24;
+
+const HIERARCHICAL_SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
+
+/** Renders a theme's own colours so the row previews what it selects. `auto` has no theme
+ * of its own — it defers to ghostty's defaults — so it shows a neutral placeholder. */
+function ThemeSwatch({ theme }: { theme?: { background?: string; foreground?: string } }) {
+ return (
+
+
+ {theme?.foreground ? (
+
+ a
+
+ ) : null}
+
+ );
+}
+
+/** Client-side terminal appearance: font size and color scheme. */
+export function TerminalAppearanceScreen(): React.ReactNode {
+ const t = useTranslations('mobile.terminalAppearance');
+ const fontSize = useTerminalPrefsStore((state) => state.fontSize);
+ const colorScheme = useTerminalPrefsStore((state) => state.colorScheme);
+ const setFontSize = useTerminalPrefsStore((state) => state.setFontSize);
+ const setColorScheme = useTerminalPrefsStore((state) => state.setColorScheme);
+
+ return (
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/settings/terminal-appearance-screen.tsx b/apps/mobile/src/components/settings/terminal-appearance-screen.tsx
new file mode 100644
index 000000000..c51b77dd4
--- /dev/null
+++ b/apps/mobile/src/components/settings/terminal-appearance-screen.tsx
@@ -0,0 +1,123 @@
+import {
+ Box,
+ Column,
+ RadioButton,
+ Row,
+ SegmentedButton,
+ SingleChoiceSegmentedButtonRow,
+ Text,
+} from '@expo/ui/jetpack-compose';
+import {
+ background,
+ border,
+ clip,
+ fillMaxWidth,
+ padding,
+ Shapes,
+ selectable,
+ selectableGroup,
+ size,
+} from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { FormList } from '@mobile/components/form/list.android';
+import { FormSection } from '@mobile/components/form/section.android';
+import {
+ resolveTerminalTheme,
+ TERMINAL_COLOR_SCHEMES,
+ TERMINAL_FONT_SIZES,
+ useTerminalPrefsStore,
+} from '@mobile/stores/terminal-prefs-store';
+import { useTranslations } from 'use-intl';
+
+const SWATCH = 24;
+
+/** Renders a theme's own colours so the row previews what it selects. `auto` has no theme
+ * of its own — it defers to ghostty's defaults — so it shows a neutral placeholder. */
+function ThemeSwatch({ theme }: { theme?: { background?: string; foreground?: string } }) {
+ const colors = useAppMaterialColors();
+
+ return (
+
+ {theme?.foreground ? (
+
+ a
+
+ ) : null}
+
+ );
+}
+
+/** Android terminal appearance, mirroring `terminal-appearance-screen.ios.tsx`: the segmented
+ * font-size picker maps to MD3 segmented buttons; the inline color-scheme picker becomes a
+ * radio group, which draws the selection state the checkmarks carried on iOS. */
+export function TerminalAppearanceScreen(): React.ReactNode {
+ const t = useTranslations('mobile.terminalAppearance');
+ const colors = useAppMaterialColors();
+ const fontSize = useTerminalPrefsStore((state) => state.fontSize);
+ const colorScheme = useTerminalPrefsStore((state) => state.colorScheme);
+ const setFontSize = useTerminalPrefsStore((state) => state.setFontSize);
+ const setColorScheme = useTerminalPrefsStore((state) => state.setColorScheme);
+
+ return (
+
+
+
+ {TERMINAL_FONT_SIZES.map((candidate) => (
+ setFontSize(candidate)}
+ >
+
+ {String(candidate)}
+
+
+ ))}
+
+
+
+
+
+ {TERMINAL_COLOR_SCHEMES.map((scheme) => {
+ const theme = resolveTerminalTheme(scheme);
+ const selected = colorScheme === scheme;
+ return (
+ setColorScheme(scheme), 'radioButton'),
+ fillMaxWidth(),
+ padding(16, 8, 16, 8),
+ ]}
+ >
+
+
+ {scheme === 'auto' ? (
+
+ {t('colorSchemeAuto')}
+
+ {t('colorSchemeAutoHint')}
+
+
+ ) : (
+ {scheme}
+ )}
+
+ );
+ })}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/shell/app-loading-screen.tsx b/apps/mobile/src/components/shell/app-loading-screen.tsx
new file mode 100644
index 000000000..f0cb66647
--- /dev/null
+++ b/apps/mobile/src/components/shell/app-loading-screen.tsx
@@ -0,0 +1,16 @@
+import { useNativePalette } from '@mobile/components/theme/native-palette';
+import { ActivityIndicator, View } from 'react-native';
+import { BrandMark } from './brand-mark';
+
+export function AppLoadingScreen(): React.ReactNode {
+ const palette = useNativePalette();
+ return (
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/shell/app-providers.tsx b/apps/mobile/src/components/shell/app-providers.tsx
index 0e0c96cf2..8f1e408e7 100644
--- a/apps/mobile/src/components/shell/app-providers.tsx
+++ b/apps/mobile/src/components/shell/app-providers.tsx
@@ -1,3 +1,5 @@
+import { useResolvedColorScheme } from '@mobile/components/theme/use-color-scheme';
+import { DarkTheme, DefaultTheme, ThemeProvider } from 'expo-router';
import { HeroUINativeProvider } from 'heroui-native';
import { KeyboardProvider } from 'react-native-keyboard-controller';
import { IntlProvider } from 'use-intl';
@@ -5,7 +7,12 @@ import { IntlProvider } from 'use-intl';
/** Children-optional wrappers so the root layout can compose providers whose own
* props declare `children` as required (mirrors workbench's AppI18nProvider pattern). */
export function AppThemeProvider({ children }: React.PropsWithChildren): React.ReactNode {
- return {children};
+ const scheme = useResolvedColorScheme();
+ return (
+
+ {children}
+
+ );
}
/** Feeds keyboard frames to `KeyboardStickyView` so the composer tracks the keyboard. */
diff --git a/apps/mobile/src/components/shell/header-icon-button.tsx b/apps/mobile/src/components/shell/header-icon-button.tsx
deleted file mode 100644
index abab1ba4a..000000000
--- a/apps/mobile/src/components/shell/header-icon-button.tsx
+++ /dev/null
@@ -1,29 +0,0 @@
-import { useThemeColor } from 'heroui-native';
-import type { LucideIcon } from 'lucide-react-native';
-import { Pressable } from 'react-native';
-
-/** Icon-only tap target for navigation headers. */
-export function HeaderIconButton({
- icon: Icon,
- label,
- onPress,
-}: {
- icon: LucideIcon;
- /** Accessibility label — icon buttons carry no visible text. */
- label: string;
- onPress: () => void;
-}): React.ReactNode {
- const foreground = useThemeColor('foreground');
- return (
- ({ opacity: pressed ? 0.4 : 1 })}
- >
-
-
- );
-}
diff --git a/apps/mobile/src/components/shell/header-menu-button.ios.ts b/apps/mobile/src/components/shell/header-menu-button.ios.ts
new file mode 100644
index 000000000..9f1e57aa1
--- /dev/null
+++ b/apps/mobile/src/components/shell/header-menu-button.ios.ts
@@ -0,0 +1,7 @@
+import type { HeaderMenuButtonProps } from '@mobile/components/shell/header-menu-button.types';
+
+/** iOS renders header menus as native `UIMenu` bar items; this twin exists so shared files can
+ * reference one component name on both platforms. */
+export function HeaderMenuButton(_props: HeaderMenuButtonProps): React.ReactNode {
+ return null;
+}
diff --git a/apps/mobile/src/components/shell/header-menu-button.tsx b/apps/mobile/src/components/shell/header-menu-button.tsx
new file mode 100644
index 000000000..602b09d20
--- /dev/null
+++ b/apps/mobile/src/components/shell/header-menu-button.tsx
@@ -0,0 +1,40 @@
+import { DropdownMenu, DropdownMenuItem, Icon, IconButton, Text } from '@expo/ui/jetpack-compose';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import type { HeaderMenuButtonProps } from '@mobile/components/shell/header-menu-button.types';
+import { useState } from 'react';
+import moreVertGlyph from '../../../assets/icons/more-vert.xml';
+
+/** Android header overflow: an MD3 `DropdownMenu` anchored to its trigger — the Compose twin of
+ * the iOS `UIMenu` bar item. The menu's trigger is fully controlled and the anchor is the
+ * non-Items child, so the IconButton opens it explicitly. */
+export function HeaderMenuButton({ label, actions }: HeaderMenuButtonProps): React.ReactNode {
+ const [expanded, setExpanded] = useState(false);
+ const colors = useAppMaterialColors();
+
+ return (
+
+ setExpanded(false)}>
+ setExpanded(true)}>
+
+
+
+ {actions.map((action) => (
+ {
+ setExpanded(false);
+ action.onPress();
+ }}
+ >
+
+ {action.label}
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/shell/header-menu-button.types.ts b/apps/mobile/src/components/shell/header-menu-button.types.ts
new file mode 100644
index 000000000..3da4a4f40
--- /dev/null
+++ b/apps/mobile/src/components/shell/header-menu-button.types.ts
@@ -0,0 +1,12 @@
+export interface HeaderMenuAction {
+ id: string;
+ label: string;
+ destructive?: boolean;
+ onPress: () => void;
+}
+
+export interface HeaderMenuButtonProps {
+ /** Accessibility name of the trigger — icon buttons carry no visible text. */
+ label: string;
+ actions: HeaderMenuAction[];
+}
diff --git a/apps/mobile/src/components/shell/primary-action-fab.ios.ts b/apps/mobile/src/components/shell/primary-action-fab.ios.ts
new file mode 100644
index 000000000..884a73a1e
--- /dev/null
+++ b/apps/mobile/src/components/shell/primary-action-fab.ios.ts
@@ -0,0 +1,7 @@
+import type { PrimaryAction } from '@mobile/components/shell/primary-action';
+
+/** iOS carries the primary action in the header bar items (pre-26) or the tab-bar slot (26+);
+ * this twin exists so the shared tab routes can reference one component name on both platforms. */
+export function PrimaryActionFab(_props: { action: PrimaryAction | null }): React.ReactNode {
+ return null;
+}
diff --git a/apps/mobile/src/components/shell/primary-action-fab.tsx b/apps/mobile/src/components/shell/primary-action-fab.tsx
new file mode 100644
index 000000000..39624d134
--- /dev/null
+++ b/apps/mobile/src/components/shell/primary-action-fab.tsx
@@ -0,0 +1,23 @@
+import { FloatingActionButton, Icon } from '@expo/ui/jetpack-compose';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import type { PrimaryAction } from '@mobile/components/shell/primary-action';
+import { View } from 'react-native';
+import addGlyph from '../../../assets/icons/add.xml';
+
+/** The tab's primary action as an MD3 floating action button at the screen's bottom-right — the
+ * Android counterpart of the iOS header bar item / iOS-26 tab-bar slot. */
+export function PrimaryActionFab({ action }: { action: PrimaryAction | null }): React.ReactNode {
+ if (!action) return null;
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/shell/root-navigator.tsx b/apps/mobile/src/components/shell/root-navigator.tsx
index 2f5a79f98..5471b167b 100644
--- a/apps/mobile/src/components/shell/root-navigator.tsx
+++ b/apps/mobile/src/components/shell/root-navigator.tsx
@@ -1,3 +1,4 @@
+import { AppLoadingScreen } from '@mobile/components/shell/app-loading-screen';
import { HostConnectionScope } from '@mobile/components/shell/host-connection-scope';
import {
useStackScreenOptions,
@@ -11,7 +12,7 @@ import { Stack } from 'expo-router';
export function RootNavigator(): React.ReactNode {
const configurationReady = useMobileConfiguration();
const screenOptions = useStackScreenOptions();
- if (!configurationReady) return null;
+ if (!configurationReady) return ;
return (
@@ -19,11 +20,15 @@ export function RootNavigator(): React.ReactNode {
name="add-host"
options={{
...VISIBLE_HEADER_OPTIONS,
- presentation: 'formSheet',
- sheetAllowedDetents: [1],
- sheetGrabberVisible: false,
- headerBackVisible: false,
title: '',
+ // iOS-only: Android's formSheet drops the native header (no title, no dismissal),
+ // so the deep-linked route pushes with the standard toolbar there instead.
+ ...(process.env.EXPO_OS === 'ios' && {
+ presentation: 'formSheet' as const,
+ sheetAllowedDetents: [1],
+ sheetGrabberVisible: false,
+ headerBackVisible: false,
+ }),
}}
/>
diff --git a/apps/mobile/src/components/shell/theme-controller.tsx b/apps/mobile/src/components/shell/theme-controller.tsx
index e829177bd..337091fec 100644
--- a/apps/mobile/src/components/shell/theme-controller.tsx
+++ b/apps/mobile/src/components/shell/theme-controller.tsx
@@ -1,6 +1,7 @@
import { useSettingsStore } from '@mobile/stores/settings-store';
import { StatusBar } from 'expo-status-bar';
import { useEffect } from 'react';
+import { Appearance } from 'react-native';
import { Uniwind, useUniwind } from 'uniwind';
/**
@@ -13,6 +14,7 @@ export function ThemeController(): React.ReactNode {
useEffect(() => {
Uniwind.setTheme(preference);
+ Appearance.setColorScheme(preference === 'system' ? 'unspecified' : preference);
}, [preference]);
return ;
diff --git a/apps/mobile/src/components/shell/use-stack-screen-options.ts b/apps/mobile/src/components/shell/use-stack-screen-options.ts
index 28d7ad2ed..58f6d4aa8 100644
--- a/apps/mobile/src/components/shell/use-stack-screen-options.ts
+++ b/apps/mobile/src/components/shell/use-stack-screen-options.ts
@@ -1,5 +1,5 @@
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import type { Stack } from 'expo-router';
-import { useThemeColor } from 'heroui-native';
import { NavigationBarBackdrop } from '../../../modules/linkcode-navigation-bar-backdrop';
import { USES_IOS_26_NAVIGATION } from './ios-26-navigation';
@@ -29,23 +29,25 @@ export const LARGE_TITLE_HEADER_OPTIONS: StackScreenOptions = {
headerShown: true,
};
-/** Theme-synced native-stack chrome; every Stack in the app spreads these defaults.
- * Screens opt into a header per route via `Stack.Screen` options. */
+/** Native-color stack chrome; every Stack in the app spreads these defaults. Screens opt into a
+ * header per route via `Stack.Screen` options. Colors come from the platform-split native
+ * palette: UIKit system colors on iOS, Material You roles on Android. */
export function useStackScreenOptions(): StackScreenOptions {
- const [background, foreground, accent] = useThemeColor(['background', 'foreground', 'accent']);
+ const palette = useNativePalette();
return {
headerShown: false,
headerLargeTitleEnabled: false,
- headerTintColor: accent,
- headerTitleStyle: { color: foreground },
- headerLargeTitleStyle: { color: foreground },
+ headerTintColor: palette.tint,
+ headerTitleStyle: { color: palette.text },
+ headerLargeTitleStyle: { color: palette.text },
+ // Android has no header blur; painting the header the content color keeps the screen
+ // reading as one surface (the shadow is already off).
...(process.env.EXPO_OS !== 'ios' && {
- headerBlurEffect: 'systemChromeMaterial' as const,
- headerStyle: { backgroundColor: 'transparent' },
+ headerStyle: { backgroundColor: palette.background },
}),
headerShadowVisible: false,
headerBackButtonDisplayMode: 'minimal',
- contentStyle: { backgroundColor: background },
+ contentStyle: { backgroundColor: palette.background },
};
}
diff --git a/apps/mobile/src/components/shell/use-trailing-actions.tsx b/apps/mobile/src/components/shell/use-trailing-actions.tsx
index 58af300c5..b50aa4c87 100644
--- a/apps/mobile/src/components/shell/use-trailing-actions.tsx
+++ b/apps/mobile/src/components/shell/use-trailing-actions.tsx
@@ -1,10 +1,11 @@
-import { HeaderIconButton } from '@mobile/components/shell/header-icon-button';
+import { HeaderMenuButton } from '@mobile/components/shell/header-menu-button';
import { USES_IOS_26_NAVIGATION } from '@mobile/components/shell/ios-26-navigation';
import type { PrimaryAction } from '@mobile/components/shell/primary-action';
+import { useNativePalette } from '@mobile/components/theme/native-palette';
import type { NativeStackHeaderItem, NativeStackNavigationOptions } from 'expo-router';
import { useRouter } from 'expo-router';
-import { EllipsisIcon } from 'lucide-react-native';
-import { Platform, View } from 'react-native';
+import { SearchIcon } from 'lucide-react-native';
+import { Platform, Pressable, View } from 'react-native';
import { useTranslations } from 'use-intl';
type TrailingHeaderOptions = Pick<
@@ -12,12 +13,20 @@ type TrailingHeaderOptions = Pick<
'headerRight' | 'unstable_headerRightItems'
>;
-/** Trailing navigation-bar chrome for the tab screens: the screen's primary action — except on
- * iOS 26, whose tab-bar slot already carries it — then the overflow menu that leads to Settings.
- * Native bar items are iOS-only, so Android keeps RN header buttons. */
-export function useTrailingActions(primary: PrimaryAction | null): TrailingHeaderOptions {
+/** Trailing navigation-bar chrome for the tab screens: on iOS the screen's primary action —
+ * except on iOS 26, whose tab-bar slot already carries it — then the overflow menu that leads to
+ * Settings. Native bar items are iOS-only; Android renders the overflow as a Compose
+ * `DropdownMenu` (right-most) and carries the primary action as a floating action button
+ * (`PrimaryActionFab`). `onSearchPress` adds an Android search toggle before the overflow — iOS
+ * search lives in the native search bar instead. */
+export function useTrailingActions(
+ primary: PrimaryAction | null,
+ { onSearchPress }: { onSearchPress?: () => void } = {},
+): TrailingHeaderOptions {
const t = useTranslations('mobile.settings');
+ const tSessions = useTranslations('mobile.sessions');
const router = useRouter();
+ const palette = useNativePalette();
if (Platform.OS === 'ios') {
const items: NativeStackHeaderItem[] = [];
@@ -49,14 +58,22 @@ export function useTrailingActions(primary: PrimaryAction | null): TrailingHeade
return {
headerRight: () => (
-
- {primary ? (
-
+
+ {onSearchPress ? (
+ ({ opacity: pressed ? 0.4 : 1 })}
+ >
+
+
) : null}
- router.push('/settings')}
+ actions={[{ id: 'settings', label: t('title'), onPress: () => router.push('/settings') }]}
/>
),
diff --git a/apps/mobile/src/components/terminal/new-terminal-sheet.ios.tsx b/apps/mobile/src/components/terminal/new-terminal-sheet.ios.tsx
new file mode 100644
index 000000000..0a08ea8e8
--- /dev/null
+++ b/apps/mobile/src/components/terminal/new-terminal-sheet.ios.tsx
@@ -0,0 +1,82 @@
+import {
+ BottomSheet,
+ Button,
+ Form,
+ Host,
+ HStack,
+ Section,
+ Text,
+ TextField,
+ useNativeState,
+} from '@expo/ui/swift-ui';
+import {
+ autocorrectionDisabled,
+ disabled,
+ foregroundStyle,
+ onSubmit,
+ submitLabel,
+ textInputAutocapitalization,
+} from '@expo/ui/swift-ui/modifiers';
+import { useTranslations } from 'use-intl';
+import type { NewTerminalSheetProps } from './new-terminal-sheet.types';
+
+export function NewTerminalSheet({
+ isPresented,
+ onIsPresentedChange,
+ creating,
+ error,
+ onCreate,
+}: NewTerminalSheetProps): React.ReactNode {
+ const t = useTranslations('mobile.terminals');
+ const cwd = useNativeState('');
+
+ const create = () => {
+ if (creating) return;
+ void onCreate(cwd.get().trim()).then((created) => {
+ if (created) cwd.set('');
+ });
+ };
+
+ return (
+
+
+
+
+
+ );
+}
diff --git a/apps/mobile/src/components/terminal/new-terminal-sheet.tsx b/apps/mobile/src/components/terminal/new-terminal-sheet.tsx
index e3b9c1158..dc151fe12 100644
--- a/apps/mobile/src/components/terminal/new-terminal-sheet.tsx
+++ b/apps/mobile/src/components/terminal/new-terminal-sheet.tsx
@@ -1,40 +1,32 @@
import {
- BottomSheet,
Button,
- Form,
- Host,
- HStack,
- Section,
+ Column,
+ ModalBottomSheet,
+ OutlinedTextField,
Text,
- TextField,
useNativeState,
-} from '@expo/ui/swift-ui';
-import {
- autocorrectionDisabled,
- disabled,
- foregroundStyle,
- onSubmit,
- submitLabel,
- textInputAutocapitalization,
-} from '@expo/ui/swift-ui/modifiers';
+} from '@expo/ui/jetpack-compose';
+import { fillMaxWidth, imePadding, padding, testID } from '@expo/ui/jetpack-compose/modifiers';
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
import { useTranslations } from 'use-intl';
+import type { NewTerminalSheetProps } from './new-terminal-sheet.types';
+/** Android new-terminal sheet. Compose's ModalBottomSheet has no `isPresented` — mounting shows
+ * it — so this renders nothing while closed and maps dismissal back through the prop. */
export function NewTerminalSheet({
isPresented,
onIsPresentedChange,
creating,
error,
onCreate,
-}: {
- isPresented: boolean;
- onIsPresentedChange: (isPresented: boolean) => void;
- creating: boolean;
- error: string | null;
- onCreate: (cwd: string) => Promise;
-}): React.ReactNode {
+}: NewTerminalSheetProps): React.ReactNode {
const t = useTranslations('mobile.terminals');
+ const colors = useAppMaterialColors();
const cwd = useNativeState('');
+ if (!isPresented) return null;
+
const create = () => {
if (creating) return;
void onCreate(cwd.get().trim()).then((created) => {
@@ -43,45 +35,43 @@ export function NewTerminalSheet({
};
return (
-
-
-
-
-
+
+
+ {t('cwdPlaceholder')}
+
+
+
+ {creating ? t('creating') : t('create')}
+
+
+
+
);
}
diff --git a/apps/mobile/src/components/terminal/new-terminal-sheet.types.ts b/apps/mobile/src/components/terminal/new-terminal-sheet.types.ts
new file mode 100644
index 000000000..d9ee6e422
--- /dev/null
+++ b/apps/mobile/src/components/terminal/new-terminal-sheet.types.ts
@@ -0,0 +1,7 @@
+export interface NewTerminalSheetProps {
+ isPresented: boolean;
+ onIsPresentedChange: (isPresented: boolean) => void;
+ creating: boolean;
+ error: string | null;
+ onCreate: (cwd: string) => Promise;
+}
diff --git a/apps/mobile/src/components/terminal/terminals-screen.ios.tsx b/apps/mobile/src/components/terminal/terminals-screen.ios.tsx
new file mode 100644
index 000000000..c3bf6be7e
--- /dev/null
+++ b/apps/mobile/src/components/terminal/terminals-screen.ios.tsx
@@ -0,0 +1,102 @@
+import { Button, ContentUnavailableView, Form, Host, Section, Text } from '@expo/ui/swift-ui';
+import { foregroundStyle, refreshable } from '@expo/ui/swift-ui/modifiers';
+import { repositoryLabel } from '@linkcode/ui/native';
+import { LoadingView } from '@mobile/components/form/loading-view.ios';
+import { NavigationRow } from '@mobile/components/form/navigation-row';
+import { NewTerminalSheet } from '@mobile/components/terminal/new-terminal-sheet';
+import { useTerminalInbox } from '@mobile/runtime/use-terminal-inbox';
+import { Platform } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+const SECONDARY = foregroundStyle({ type: 'hierarchical', style: 'secondary' });
+const SUPPORTS_CONTENT_UNAVAILABLE_VIEW =
+ Platform.OS === 'ios' && Number.parseInt(Platform.Version, 10) >= 17;
+
+/** Host terminal inbox body: attach to a running PTY or start a new one on the host. */
+export function TerminalsScreen({
+ sheetOpen,
+ onSheetOpenChange,
+}: {
+ sheetOpen: boolean;
+ onSheetOpenChange: (open: boolean) => void;
+}): React.ReactNode {
+ const t = useTranslations('mobile.terminals');
+ const {
+ terminals,
+ loading,
+ loadError,
+ createError,
+ creating,
+ onRefresh,
+ retry,
+ openTerminal,
+ onCreate,
+ } = useTerminalInbox(() => onSheetOpenChange(false));
+
+ return (
+ <>
+ {/* Form needs the viewport as its proposed size, otherwise it collapses to its content. */}
+
+ {loading ? (
+
+ ) : loadError && terminals.length === 0 ? (
+
+ ) : terminals.length === 0 ? (
+ SUPPORTS_CONTENT_UNAVAILABLE_VIEW ? (
+
+ ) : (
+
+ )
+ ) : (
+
+ )}
+
+ {
+ onSheetOpenChange(open);
+ }}
+ creating={creating}
+ error={createError}
+ onCreate={onCreate}
+ />
+ >
+ );
+}
diff --git a/apps/mobile/src/components/terminal/terminals-screen.tsx b/apps/mobile/src/components/terminal/terminals-screen.tsx
new file mode 100644
index 000000000..a6d6bc827
--- /dev/null
+++ b/apps/mobile/src/components/terminal/terminals-screen.tsx
@@ -0,0 +1,93 @@
+import { Button, LazyColumn, PullToRefreshBox, Text } from '@expo/ui/jetpack-compose';
+import { fillMaxWidth, padding } from '@expo/ui/jetpack-compose/modifiers';
+import { repositoryLabel } from '@linkcode/ui/native';
+import { FormList } from '@mobile/components/form/list.android';
+import { LoadingView } from '@mobile/components/form/loading-view.android';
+import { NavigationRow } from '@mobile/components/form/navigation-row';
+import { FormHint } from '@mobile/components/form/rows.android';
+import { ThemedHost } from '@mobile/components/form/themed-host.android';
+import { NewTerminalSheet } from '@mobile/components/terminal/new-terminal-sheet';
+import { useTerminalInbox } from '@mobile/runtime/use-terminal-inbox';
+import { useState } from 'react';
+import { useTranslations } from 'use-intl';
+
+/** Android terminal inbox body, mirroring `terminals-screen.ios.tsx` with MD3 list rows and a
+ * controlled PullToRefreshBox in place of SwiftUI's awaiting `refreshable`. */
+export function TerminalsScreen({
+ sheetOpen,
+ onSheetOpenChange,
+}: {
+ sheetOpen: boolean;
+ onSheetOpenChange: (open: boolean) => void;
+}): React.ReactNode {
+ const t = useTranslations('mobile.terminals');
+ const {
+ terminals,
+ loading,
+ loadError,
+ createError,
+ creating,
+ onRefresh,
+ retry,
+ openTerminal,
+ onCreate,
+ } = useTerminalInbox(() => onSheetOpenChange(false));
+ const [refreshing, setRefreshing] = useState(false);
+
+ const refresh = () => {
+ setRefreshing(true);
+ void onRefresh().finally(() => setRefreshing(false));
+ };
+
+ return (
+ <>
+ {loading ? (
+
+ ) : loadError && terminals.length === 0 ? (
+
+ {t('loadError', { error: loadError })}
+
+ {t('retry')}
+
+
+ ) : (
+
+ {/* topCenter: see thread-list.tsx — restores the indicator alignment expo-ui drops. */}
+
+
+ {loadError ? (
+ {t('loadError', { error: loadError })}
+ ) : null}
+ {terminals.length === 0 ? (
+ {t('emptyHint')}
+ ) : (
+ terminals.map((terminal) => (
+ openTerminal(terminal.terminalId)}
+ />
+ ))
+ )}
+
+
+
+ )}
+
+ >
+ );
+}
diff --git a/apps/mobile/src/components/theme/native-palette.ios.ts b/apps/mobile/src/components/theme/native-palette.ios.ts
new file mode 100644
index 000000000..857577af8
--- /dev/null
+++ b/apps/mobile/src/components/theme/native-palette.ios.ts
@@ -0,0 +1,24 @@
+import type { NativePalette } from '@mobile/components/theme/native-palette.types';
+import { Color } from 'expo-router';
+
+/** iOS resolves the palette from UIKit semantic colors — `PlatformColor` values that adapt to
+ * light/dark natively, so no hook state is needed. White-on-blue matches UIKit's own filled
+ * controls, which never invert their glyph. */
+const IOS_PALETTE: NativePalette = {
+ background: Color.ios.systemBackground,
+ groupedBackground: Color.ios.systemGroupedBackground,
+ surface: Color.ios.secondarySystemBackground,
+ text: Color.ios.label,
+ textSecondary: Color.ios.secondaryLabel,
+ tint: Color.ios.systemBlue,
+ onTint: '#FFFFFF',
+ outline: Color.ios.separator,
+ danger: Color.ios.systemRed,
+ success: Color.ios.systemGreen,
+ warning: Color.ios.systemOrange,
+};
+
+// eslint-disable-next-line @eslint-react/no-unnecessary-use-prefix -- the Android twin resolves its palette through hooks; the shared contract keeps the hook name
+export function useNativePalette(): NativePalette {
+ return IOS_PALETTE;
+}
diff --git a/apps/mobile/src/components/theme/native-palette.ts b/apps/mobile/src/components/theme/native-palette.ts
new file mode 100644
index 000000000..6ee54d4df
--- /dev/null
+++ b/apps/mobile/src/components/theme/native-palette.ts
@@ -0,0 +1,22 @@
+import { useAppMaterialColors } from '@mobile/components/form/compose-theme.android';
+import type { NativePalette } from '@mobile/components/theme/native-palette.types';
+
+/** Android resolves the palette from Material You dynamic roles (the same scheme the Compose
+ * surfaces render), pinned to the app theme. */
+export function useNativePalette(): NativePalette {
+ const colors = useAppMaterialColors();
+
+ return {
+ background: colors.surface,
+ groupedBackground: colors.surface,
+ surface: colors.surfaceContainerHigh,
+ text: colors.onSurface,
+ textSecondary: colors.onSurfaceVariant,
+ tint: colors.primary,
+ onTint: colors.onPrimary,
+ outline: colors.outlineVariant,
+ danger: colors.error,
+ success: colors.primary,
+ warning: colors.tertiary,
+ };
+}
diff --git a/apps/mobile/src/components/theme/native-palette.types.ts b/apps/mobile/src/components/theme/native-palette.types.ts
new file mode 100644
index 000000000..d9595b3e1
--- /dev/null
+++ b/apps/mobile/src/components/theme/native-palette.types.ts
@@ -0,0 +1,25 @@
+import type { ColorValue } from 'react-native';
+
+/** Semantic palette bridging RN-drawn surfaces to each platform's NATIVE colors: adaptive
+ * `PlatformColor` system colors on iOS, Material You dynamic roles on Android. Components take
+ * roles from here instead of the app's own brand tokens. */
+export interface NativePalette {
+ /** Screens and navigation chrome. */
+ background: ColorValue;
+ /** Grouped list/form screens, and the load states that stand in for them. */
+ groupedBackground: ColorValue;
+ /** Raised cards: composer, bubbles, prompt cards. */
+ surface: ColorValue;
+ text: ColorValue;
+ textSecondary: ColorValue;
+ /** Interactive accent (send, header tint). */
+ tint: ColorValue;
+ /** Content drawn on top of `tint`. */
+ onTint: ColorValue;
+ /** Hairline borders and idle indicators. */
+ outline: ColorValue;
+ danger: ColorValue;
+ /** Status accents; Android maps to primary/tertiary — MD3 has no success/warning roles. */
+ success: ColorValue;
+ warning: ColorValue;
+}
diff --git a/apps/mobile/src/components/theme/use-color-scheme.ts b/apps/mobile/src/components/theme/use-color-scheme.ts
new file mode 100644
index 000000000..eee45843e
--- /dev/null
+++ b/apps/mobile/src/components/theme/use-color-scheme.ts
@@ -0,0 +1,9 @@
+import { useSettingsStore } from '@mobile/stores/settings-store';
+import { useColorScheme } from 'react-native';
+
+export function useResolvedColorScheme(): 'light' | 'dark' {
+ const preference = useSettingsStore((state) => state.themePreference);
+ const system = useColorScheme();
+ if (preference === 'system') return system === 'dark' ? 'dark' : 'light';
+ return preference;
+}
diff --git a/apps/mobile/src/runtime/create-host-transport.ts b/apps/mobile/src/runtime/create-host-transport.ts
index d213c4c44..dcbe064b0 100644
--- a/apps/mobile/src/runtime/create-host-transport.ts
+++ b/apps/mobile/src/runtime/create-host-transport.ts
@@ -3,10 +3,14 @@ import { SocketIoTransport, TunnelTransport, WsTransport } from '@linkcode/trans
import type { HostProfile } from '@mobile/stores/host-store';
import { CLOUD_URL, fetchTunnelJwt } from './cloud/client';
+type MockModule = typeof import('@linkcode/client-core/mock');
+
/**
* Transport by host kind: tunnel entries dial the cloud relay; direct entries pick by
* URL scheme — http(s) is the daemon's default Socket.IO listener (same as
- * webview/desktop), ws(s) a raw WebSocket listener.
+ * webview/desktop), ws(s) a raw WebSocket listener, and `mock:` the in-process mock host
+ * (webview's `dev:mock` showcase). Dev builds only: the `__DEV__` guard keeps Metro from
+ * bundling the mock into release, where the throw surfaces as a plain connection failure.
*/
export function createHostTransport(host: HostProfile): Transport {
if ('tunnelHostId' in host) {
@@ -18,6 +22,14 @@ export function createHostTransport(host: HostProfile): Transport {
});
}
const { protocol } = new URL(host.url);
+ if (protocol === 'mock:') {
+ if (__DEV__) {
+ // eslint-disable-next-line @typescript-eslint/no-require-imports -- static import would bundle the mock into release builds
+ const mock = require('@linkcode/client-core/mock') as MockModule;
+ return mock.createDevMockTransport();
+ }
+ throw new Error('mock:// hosts are only available in development builds');
+ }
return protocol === 'ws:' || protocol === 'wss:'
? new WsTransport({ url: host.url })
: new SocketIoTransport({ url: host.url });
diff --git a/apps/mobile/src/runtime/use-add-host.ts b/apps/mobile/src/runtime/use-add-host.ts
new file mode 100644
index 000000000..2435188d7
--- /dev/null
+++ b/apps/mobile/src/runtime/use-add-host.ts
@@ -0,0 +1,39 @@
+import { HostUrlSchema, useHostRegistryStore } from '@mobile/stores/host-store';
+import { useRouter } from 'expo-router';
+import { useState } from 'react';
+import { useTranslations } from 'use-intl';
+
+/** Validation + submit flow of the add-host form. Field text stays in each platform view's own
+ * `useNativeState` (the shared `@expo/ui` State export is the web polyfill, typed without
+ * `.get()`), so submit takes the values instead of owning them. */
+export function useAddHost(): {
+ urlInvalid: boolean;
+ urlValid: boolean;
+ onUrlChange: (text: string) => void;
+ submit: (rawName: string, rawUrl: string) => void;
+} {
+ const t = useTranslations('mobile.connect');
+ const router = useRouter();
+ const addHost = useHostRegistryStore((state) => state.addHost);
+ const setLastActiveHostId = useHostRegistryStore((state) => state.setLastActiveHostId);
+ const [urlInvalid, setUrlInvalid] = useState(false);
+ const [urlValid, setUrlValid] = useState(false);
+
+ const onUrlChange = (text: string) => {
+ setUrlInvalid(false);
+ setUrlValid(HostUrlSchema.safeParse(text.trim()).success);
+ };
+
+ const submit = (rawName: string, rawUrl: string) => {
+ const trimmedUrl = rawUrl.trim();
+ if (!HostUrlSchema.safeParse(trimmedUrl).success) {
+ setUrlInvalid(true);
+ return;
+ }
+ const profile = addHost({ name: rawName.trim() || t('namePlaceholder'), url: trimmedUrl });
+ setLastActiveHostId(profile.id);
+ router.dismissTo('/threads');
+ };
+
+ return { urlInvalid, urlValid, onUrlChange, submit };
+}
diff --git a/apps/mobile/src/runtime/use-delete-account.ts b/apps/mobile/src/runtime/use-delete-account.ts
new file mode 100644
index 000000000..b3edbbf91
--- /dev/null
+++ b/apps/mobile/src/runtime/use-delete-account.ts
@@ -0,0 +1,80 @@
+import { deleteAccount, runAccountDeletionTeardown } from '@mobile/runtime/cloud/deletion';
+import { useState } from 'react';
+import { Alert } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+export interface DeleteAccountState {
+ busy: boolean;
+ confirmDelete: () => void;
+}
+
+/** Shared deletion flow for both platform account screens. Confirmation is an RN `Alert`
+ * because that already is the native alert on iOS and Android. */
+export function useDeleteAccount(): DeleteAccountState {
+ const t = useTranslations('mobile.account');
+ const [busy, setBusy] = useState(false);
+
+ const failureMessage = (code: string | undefined): string => {
+ if (code === 'ACCOUNT_DELETION_EMERGENCY_AUDIT_HOLD') return t('deleteEmergencyHold');
+ if (code === 'ACCOUNT_DELETION_SOLE_ORGANIZATION_OWNER') return t('deleteSoleOwner');
+ return t('deleteFailed');
+ };
+
+ const run = async () => {
+ setBusy(true);
+ try {
+ const outcome = await deleteAccount();
+ if (outcome.kind === 'reauthentication-failed') {
+ Alert.alert(t('deleteReauthenticationFailed'));
+ return;
+ }
+ if (outcome.kind === 'apple-device-required') {
+ Alert.alert(t('deleteAppleDeviceRequired'));
+ return;
+ }
+ if (outcome.kind === 'account-mismatch') {
+ Alert.alert(t('deleteAccountMismatch'));
+ return;
+ }
+ if (outcome.kind === 'unknown') {
+ Alert.alert(t('deleteUnknown'));
+ return;
+ }
+ if (outcome.kind === 'failed') {
+ Alert.alert(failureMessage(outcome.code));
+ return;
+ }
+
+ // Both remaining outcomes (`pending` and `completed`) mean the server
+ // already accepted the deletion — local teardown runs regardless.
+ await runAccountDeletionTeardown();
+ if (outcome.kind === 'pending') {
+ Alert.alert(t('deletePending'));
+ return;
+ }
+ // A failed revocation still leaves deletion successful and needs manual Apple follow-up.
+ if (outcome.authorizationRevocation === 'failed') {
+ Alert.alert(t('deleteRevocationFailedTitle'), t('deleteRevocationFailedMessage'));
+ } else {
+ Alert.alert(t('deleteCompleted'));
+ }
+ } finally {
+ setBusy(false);
+ }
+ };
+
+ const confirmDelete = () => {
+ Alert.alert(t('deleteTitle'), t('deleteMessage'), [
+ { text: t('deleteCancel'), style: 'cancel' },
+ {
+ text: t('deleteConfirm'),
+ style: 'destructive',
+ onPress() {
+ void run();
+ },
+ },
+ ]);
+ };
+
+ return { busy, confirmDelete };
+}
diff --git a/apps/mobile/src/runtime/use-devices-section.ts b/apps/mobile/src/runtime/use-devices-section.ts
new file mode 100644
index 000000000..54df8c2f8
--- /dev/null
+++ b/apps/mobile/src/runtime/use-devices-section.ts
@@ -0,0 +1,91 @@
+import { signOutOfCloud } from '@mobile/runtime/cloud/account';
+import type { CloudDevice } from '@mobile/runtime/cloud/devices';
+import { fetchDevices, getEnrolledDeviceId, revokeDevice } from '@mobile/runtime/cloud/devices';
+import { formatRelativeShort } from '@mobile/utils/relative-time';
+import { noop } from 'foxact/noop';
+import { useCallback, useEffect, useState } from 'react';
+import { Alert } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+export interface DevicesSectionState {
+ devices: CloudDevice[] | null;
+ devicesError: boolean;
+ enrolledId: string | null;
+ busyId: string | null;
+ refresh: () => void;
+ confirmRevoke: (device: CloudDevice) => void;
+ describeDevice: (device: CloudDevice) => string;
+}
+
+/** View-model of the account's device registry, shared by both platform views. Revoking cuts
+ * access to new tunnel tokens; revoking this phone also signs it out (the cloud kills its
+ * sessions). Confirmation stays an RN `Alert` — that already is the native alert on both. */
+export function useDevicesSection(): DevicesSectionState {
+ const t = useTranslations('mobile.account');
+
+ const [devices, setDevices] = useState(null);
+ const [devicesError, setDevicesError] = useState(false);
+ const [enrolledId, setEnrolledId] = useState(null);
+ const [busyId, setBusyId] = useState(null);
+
+ const load = useCallback(() => {
+ fetchDevices()
+ .then(setDevices)
+ .catch(() => setDevicesError(true));
+ }, []);
+
+ useEffect(() => {
+ getEnrolledDeviceId().then(setEnrolledId).catch(noop);
+ load();
+ }, [load]);
+
+ const refresh = () => {
+ setDevicesError(false);
+ setDevices(null);
+ load();
+ };
+
+ const revoke = async (device: CloudDevice) => {
+ setBusyId(device.id);
+ try {
+ await revokeDevice(device.id);
+ if (device.id === enrolledId) {
+ // The device revoke already removed its push token and killed this phone's sessions.
+ await signOutOfCloud({ revokePushToken: false }).catch(noop);
+ return;
+ }
+ refresh();
+ } catch {
+ Alert.alert(t('revokeError'));
+ } finally {
+ setBusyId(null);
+ }
+ };
+
+ const confirmRevoke = (device: CloudDevice) => {
+ Alert.alert(
+ t('revokeTitle', { name: device.name }),
+ device.id === enrolledId ? t('revokeThisDeviceMessage') : t('revokeMessage'),
+ [
+ { text: t('revokeCancel'), style: 'cancel' },
+ {
+ text: t('revoke'),
+ style: 'destructive',
+ onPress() {
+ void revoke(device);
+ },
+ },
+ ],
+ );
+ };
+
+ const describeDevice = (device: CloudDevice): string => {
+ const kind = t(`deviceKind.${device.kind}`);
+ const platform = device.platform ? `${kind} · ${device.platform}` : kind;
+ return device.lastSeenAt
+ ? `${platform} · ${t('lastSeen', { time: formatRelativeShort(new Date(device.lastSeenAt).getTime()) })}`
+ : platform;
+ };
+
+ return { devices, devicesError, enrolledId, busyId, refresh, confirmRevoke, describeDevice };
+}
diff --git a/apps/mobile/src/runtime/use-discovered-hosts.ts b/apps/mobile/src/runtime/use-discovered-hosts.ts
new file mode 100644
index 000000000..c163f8d0f
--- /dev/null
+++ b/apps/mobile/src/runtime/use-discovered-hosts.ts
@@ -0,0 +1,27 @@
+import { canonicalDirectHostUrl } from '@mobile/runtime/daemon-discovery';
+import { useDaemonDiscovery } from '@mobile/runtime/use-daemon-discovery';
+import { useOpenHost } from '@mobile/runtime/use-open-host';
+import { useHostRegistryStore } from '@mobile/stores/host-store';
+
+/** Discovered LAN daemons minus the ones already saved, plus the save-and-open action —
+ * shared by both platform views of the discovered-hosts section. */
+export function useDiscoveredHosts() {
+ const { hosts, status } = useDaemonDiscovery();
+ const openHost = useOpenHost();
+ const savedHosts = useHostRegistryStore((state) => state.hosts);
+ const addHost = useHostRegistryStore((state) => state.addHost);
+
+ const savedUrls = new Set();
+ for (let i = 0, len = savedHosts.length; i < len; i++) {
+ const host = savedHosts[i];
+ if ('url' in host) savedUrls.add(canonicalDirectHostUrl(host.url));
+ }
+ const discoveredHosts = hosts.filter((host) => !savedUrls.has(canonicalDirectHostUrl(host.url)));
+
+ const saveAndOpen = (host: (typeof discoveredHosts)[number]) => {
+ const profile = addHost({ name: host.name, url: host.url });
+ openHost(profile.id);
+ };
+
+ return { discoveredHosts, status, saveAndOpen };
+}
diff --git a/apps/mobile/src/runtime/use-my-machines.ts b/apps/mobile/src/runtime/use-my-machines.ts
new file mode 100644
index 000000000..48091de72
--- /dev/null
+++ b/apps/mobile/src/runtime/use-my-machines.ts
@@ -0,0 +1,53 @@
+import { ensureDeviceRegistered } from '@mobile/runtime/cloud/devices';
+import type { OnlineHost } from '@mobile/runtime/cloud/hosts';
+import { fetchOnlineHosts } from '@mobile/runtime/cloud/hosts';
+import { useOpenHost } from '@mobile/runtime/use-open-host';
+import { useHostRegistryStore } from '@mobile/stores/host-store';
+import { noop } from 'foxact/noop';
+import { useCallback, useEffect, useState } from 'react';
+
+export interface MyMachinesState {
+ onlineHosts: OnlineHost[] | null;
+ hostsError: boolean;
+ refresh: () => void;
+ saveAndOpen: (host: OnlineHost) => void;
+}
+
+/** Online machines (daemons connected to the relay), shared by both platform views. Tapping one
+ * saves it as a tunnel host and opens it. */
+export function useMyMachines(userId: string): MyMachinesState {
+ const openHost = useOpenHost();
+ const addTunnelHost = useHostRegistryStore((state) => state.addTunnelHost);
+
+ const [onlineHosts, setOnlineHosts] = useState(null);
+ const [hostsError, setHostsError] = useState(false);
+
+ const load = useCallback(() => {
+ fetchOnlineHosts()
+ .then(setOnlineHosts)
+ .catch(() => setHostsError(true));
+ }, []);
+
+ const refresh = () => {
+ setHostsError(false);
+ setOnlineHosts(null);
+ load();
+ };
+
+ useEffect(() => {
+ // Best-effort: registration only lists the phone under the account's
+ // devices; discovering and connecting to hosts does not depend on it.
+ ensureDeviceRegistered(userId).catch(noop);
+ load();
+ }, [userId, load]);
+
+ const saveAndOpen = (host: OnlineHost) => {
+ const profile = addTunnelHost({
+ name: host.name ?? host.hostId.slice(0, 8),
+ tunnelHostId: host.hostId,
+ });
+ openHost(profile.id);
+ };
+
+ return { onlineHosts, hostsError, refresh, saveAndOpen };
+}
diff --git a/apps/mobile/src/runtime/use-notification-settings.ts b/apps/mobile/src/runtime/use-notification-settings.ts
new file mode 100644
index 000000000..31d780252
--- /dev/null
+++ b/apps/mobile/src/runtime/use-notification-settings.ts
@@ -0,0 +1,46 @@
+import { useCloudAccount } from '@mobile/runtime/cloud/account';
+import {
+ disableDeviceNotifications,
+ enableDeviceNotifications,
+} from '@mobile/runtime/notifications';
+import { useSettingsStore } from '@mobile/stores/settings-store';
+import { useRef, useState } from 'react';
+import { Alert, Linking } from 'react-native';
+import { useTranslations } from 'use-intl';
+
+export function useNotificationSettings() {
+ const t = useTranslations('mobile.settings');
+ const account = useCloudAccount();
+ const enabled = useSettingsStore((state) => state.notificationsEnabled);
+ const [pending, setPending] = useState(false);
+ const pendingRef = useRef(false);
+
+ const update = async (enabled: boolean) => {
+ if (account.status !== 'signed-in' || pendingRef.current) return;
+ pendingRef.current = true;
+ setPending(true);
+ try {
+ if (!enabled) {
+ await disableDeviceNotifications();
+ return;
+ }
+ if (await enableDeviceNotifications(account.user.id)) return;
+ Alert.alert(t('notificationsDeniedTitle'), t('notificationsDenied'), [
+ { text: t('cancel'), style: 'cancel' },
+ {
+ text: t('openSettings'),
+ onPress() {
+ void Linking.openSettings();
+ },
+ },
+ ]);
+ } catch {
+ Alert.alert(t('notificationsErrorTitle'), t('notificationsError'));
+ } finally {
+ pendingRef.current = false;
+ setPending(false);
+ }
+ };
+
+ return { enabled, canUpdate: account.status === 'signed-in' && !pending, update };
+}
diff --git a/apps/mobile/src/runtime/use-terminal-inbox.ts b/apps/mobile/src/runtime/use-terminal-inbox.ts
new file mode 100644
index 000000000..47a454422
--- /dev/null
+++ b/apps/mobile/src/runtime/use-terminal-inbox.ts
@@ -0,0 +1,110 @@
+import { useLinkCodeClient } from '@linkcode/client-core';
+import type { TerminalMetadata } from '@linkcode/schema';
+import { useFocusEffect, useRouter } from 'expo-router';
+import { extractErrorMessage } from 'foxts/extract-error-message';
+import { useCallback, useState } from 'react';
+
+const INITIAL_TERMINAL_SIZE = { cols: 80, rows: 24 };
+
+export interface TerminalInboxState {
+ terminals: TerminalMetadata[];
+ loading: boolean;
+ loadError: string | null;
+ createError: string | null;
+ creating: boolean;
+ /** Pull-to-refresh driver; the platform view holds its spinner until this resolves. */
+ onRefresh: () => Promise;
+ /** Full-screen retry after a load error: re-enters the loading state around a refresh. */
+ retry: () => void;
+ openTerminal: (terminalId: string, takeControl?: boolean) => void;
+ onCreate: (cwd: string) => Promise;
+}
+
+/** The terminal inbox view model: attach to a running PTY or start a new one on the host.
+ * `onCreated` closes whatever surface collected the cwd (the new-terminal sheet). */
+export function useTerminalInbox(onCreated: () => void): TerminalInboxState {
+ const router = useRouter();
+ const client = useLinkCodeClient();
+ const [terminals, setTerminals] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [loadError, setLoadError] = useState(null);
+ const [createError, setCreateError] = useState(null);
+ const [creating, setCreating] = useState(false);
+
+ const load = useCallback(
+ async () => (await client.listTerminals()).sort((a, b) => b.createdAt - a.createdAt),
+ [client],
+ );
+
+ useFocusEffect(
+ useCallback(() => {
+ let active = true;
+ setLoadError(null);
+ void load()
+ .then((nextTerminals) => {
+ if (active) setTerminals(nextTerminals);
+ })
+ .catch((error_: unknown) => {
+ if (active) setLoadError(extractErrorMessage(error_, false) ?? 'Unknown error');
+ })
+ .finally(() => {
+ if (active) setLoading(false);
+ });
+ return () => {
+ active = false;
+ };
+ }, [load]),
+ );
+
+ const onRefresh = async () => {
+ setLoadError(null);
+ try {
+ setTerminals(await load());
+ } catch (error_) {
+ setLoadError(extractErrorMessage(error_, false) ?? 'Unknown error');
+ }
+ };
+
+ const retry = () => {
+ setLoading(true);
+ void onRefresh().finally(() => setLoading(false));
+ };
+
+ const openTerminal = (terminalId: string, takeControl = false) => {
+ const query = takeControl ? '?takeover=1' : '';
+ router.push(`/terminal/${encodeURIComponent(terminalId)}${query}`);
+ };
+
+ const onCreate = async (cwd: string): Promise => {
+ if (creating) return false;
+ setCreating(true);
+ setCreateError(null);
+ try {
+ const terminalId = await client.openTerminal({
+ ...INITIAL_TERMINAL_SIZE,
+ cwd: cwd || undefined,
+ });
+ client.detachTerminal(terminalId);
+ onCreated();
+ openTerminal(terminalId, true);
+ return true;
+ } catch (error_) {
+ setCreateError(extractErrorMessage(error_, false) ?? 'Unknown error');
+ return false;
+ } finally {
+ setCreating(false);
+ }
+ };
+
+ return {
+ terminals,
+ loading,
+ loadError,
+ createError,
+ creating,
+ onRefresh,
+ retry,
+ openTerminal,
+ onCreate,
+ };
+}
diff --git a/apps/mobile/src/runtime/use-thread-inbox.ts b/apps/mobile/src/runtime/use-thread-inbox.ts
new file mode 100644
index 000000000..5e436234a
--- /dev/null
+++ b/apps/mobile/src/runtime/use-thread-inbox.ts
@@ -0,0 +1,50 @@
+import { useSessions } from '@linkcode/client-core';
+import type { ThreadGroup } from '@linkcode/ui/native';
+import {
+ groupThreadsByWorkspace,
+ repositoryLabel,
+ withoutAutomationSessions,
+} from '@linkcode/ui/native';
+import { useWorkspaces } from '@mobile/runtime/use-workspaces';
+import { threadTitle } from '@mobile/utils/thread-title';
+import { useTranslations } from 'use-intl';
+
+/** The threads inbox view model: sessions grouped by workspace, filtered by the search query.
+ * The query is owned by the route — on Android it feeds the toolbar search field, on iOS the
+ * native search bar. Empty workspace groups are hidden — the new-thread page is where they
+ * surface. */
+export function useThreadInbox(query: string): {
+ loading: boolean;
+ groups: ThreadGroup[];
+ hasQuery: boolean;
+ groupLabel: (group: ThreadGroup) => string;
+ onRefresh: () => Promise;
+} {
+ const t = useTranslations('mobile.sessions');
+ const { sessions, refresh, loading } = useSessions();
+ const { workspaces, refresh: refreshWorkspaces } = useWorkspaces();
+
+ const needle = query.trim().toLowerCase();
+ const groups = groupThreadsByWorkspace(withoutAutomationSessions(sessions), workspaces).reduce<
+ ThreadGroup[]
+ >((kept, group) => {
+ const matched =
+ needle === ''
+ ? group.sessions
+ : group.sessions.filter((session) => threadTitle(session).toLowerCase().includes(needle));
+ if (matched.length > 0) kept.push({ ...group, sessions: matched });
+ return kept;
+ }, []);
+
+ const groupLabel = (group: ThreadGroup): string => {
+ if (group.isChat) return t('chats');
+ if (!group.workspace) return t('otherThreads');
+ return group.workspace.name ?? repositoryLabel(group.workspace.cwd);
+ };
+
+ const onRefresh = async () => {
+ await Promise.all([refresh(), refreshWorkspaces()]);
+ };
+
+ return { loading, groups, hasQuery: needle !== '', groupLabel, onRefresh };
+}
diff --git a/apps/mobile/src/stores/host-store.ts b/apps/mobile/src/stores/host-store.ts
index 637f7ca8c..1d06cb2d7 100644
--- a/apps/mobile/src/stores/host-store.ts
+++ b/apps/mobile/src/stores/host-store.ts
@@ -5,9 +5,12 @@ import { z } from 'zod';
import { create } from 'zustand';
import { createJSONStorage } from 'zustand/middleware';
-const HOST_URL_PROTOCOLS = new Set(['http:', 'https:', 'ws:', 'wss:']);
+// `mock:` stays accepted in release schemas so a dev-added mock host never poisons the persisted
+// registry blob; its transport is what refuses to dial outside dev builds.
+const HOST_URL_PROTOCOLS = new Set(['http:', 'https:', 'ws:', 'wss:', 'mock:']);
-/** http(s) dials the daemon's Socket.IO listener; ws(s) dials a raw WebSocket listener. */
+/** http(s) dials the daemon's Socket.IO listener; ws(s) dials a raw WebSocket listener; `mock:`
+ * runs the in-process mock host (dev builds only). */
export const HostUrlSchema = z.string().refine((value) => {
try {
return HOST_URL_PROTOCOLS.has(new URL(value).protocol);
diff --git a/apps/mobile/src/utils/thread-title.ts b/apps/mobile/src/utils/thread-title.ts
new file mode 100644
index 000000000..6b34e7192
--- /dev/null
+++ b/apps/mobile/src/utils/thread-title.ts
@@ -0,0 +1,7 @@
+import type { SessionInfo } from '@linkcode/schema';
+import { AGENT_LABELS, repositoryLabel } from '@linkcode/ui/native';
+
+/** The title a thread is listed and searched under — the same fallback the row renders. */
+export function threadTitle(session: SessionInfo): string {
+ return session.title ?? `${AGENT_LABELS[session.kind]} in ${repositoryLabel(session.cwd)}`;
+}
diff --git a/devenv.nix b/devenv.nix
index 89349230f..e08d7fad8 100644
--- a/devenv.nix
+++ b/devenv.nix
@@ -13,7 +13,7 @@
# Mobile UI e2e drives the iOS simulator, which only exists on macOS; Linux CI would pull the
# JVM closure for nothing. Pinned here rather than installed per-machine so the driver version
# is part of the toolchain like every other tool.
- ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ pkgs.maestro ];
+ ++ pkgs.lib.optionals pkgs.stdenv.isDarwin [ pkgs.maestro pkgs.cocoapods ];
languages.rust = {
enable = true;
@@ -86,7 +86,7 @@
scripts.mobile.exec = ''
export PATH="/usr/bin:$PATH" SENTRY_DISABLE_AUTO_UPLOAD=true
unset DEVELOPER_DIR SDKROOT CC CXX LD NIX_CFLAGS_COMPILE NIX_LDFLAGS MACOSX_DEPLOYMENT_TARGET
- pnpm run --filter @linkcode/mobile ios
+ pnpm run --filter @linkcode/mobile ios "$@"
'';
scripts.app.exec = "pnpm run --filter @linkcode/daemon build:rust && pnpm --filter @linkcode/daemon --filter @linkcode/desktop --parallel dev";
}
diff --git a/packages/client/core/package.json b/packages/client/core/package.json
index f9c6fd31e..8b963b845 100644
--- a/packages/client/core/package.json
+++ b/packages/client/core/package.json
@@ -6,7 +6,8 @@
"main": "./src/index.ts",
"types": "./src/index.ts",
"exports": {
- ".": "./src/index.ts"
+ ".": "./src/index.ts",
+ "./mock": "./src/mock/dev-mock-transport.ts"
},
"scripts": {
"lint": "eslint --format=sukka ."
diff --git a/packages/client/workbench/src/mock/data/commands.ts b/packages/client/core/src/mock/data/commands.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/commands.ts
rename to packages/client/core/src/mock/data/commands.ts
diff --git a/packages/client/workbench/src/mock/data/files.ts b/packages/client/core/src/mock/data/files.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/files.ts
rename to packages/client/core/src/mock/data/files.ts
diff --git a/packages/client/workbench/src/mock/data/git.ts b/packages/client/core/src/mock/data/git.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/git.ts
rename to packages/client/core/src/mock/data/git.ts
diff --git a/packages/client/workbench/src/mock/data/history.ts b/packages/client/core/src/mock/data/history.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/history.ts
rename to packages/client/core/src/mock/data/history.ts
diff --git a/packages/client/workbench/src/mock/data/long-thread.ts b/packages/client/core/src/mock/data/long-thread.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/long-thread.ts
rename to packages/client/core/src/mock/data/long-thread.ts
diff --git a/packages/client/workbench/src/mock/data/models.ts b/packages/client/core/src/mock/data/models.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/models.ts
rename to packages/client/core/src/mock/data/models.ts
diff --git a/packages/client/workbench/src/mock/data/plugins.ts b/packages/client/core/src/mock/data/plugins.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/plugins.ts
rename to packages/client/core/src/mock/data/plugins.ts
diff --git a/packages/client/workbench/src/mock/data/prompt.ts b/packages/client/core/src/mock/data/prompt.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/prompt.ts
rename to packages/client/core/src/mock/data/prompt.ts
diff --git a/packages/client/workbench/src/mock/data/scripts.ts b/packages/client/core/src/mock/data/scripts.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/scripts.ts
rename to packages/client/core/src/mock/data/scripts.ts
diff --git a/packages/client/workbench/src/mock/data/sessions.ts b/packages/client/core/src/mock/data/sessions.ts
similarity index 100%
rename from packages/client/workbench/src/mock/data/sessions.ts
rename to packages/client/core/src/mock/data/sessions.ts
diff --git a/packages/client/workbench/src/mock/data/showcase.ts b/packages/client/core/src/mock/data/showcase.ts
similarity index 98%
rename from packages/client/workbench/src/mock/data/showcase.ts
rename to packages/client/core/src/mock/data/showcase.ts
index 84d811647..763552d17 100644
--- a/packages/client/workbench/src/mock/data/showcase.ts
+++ b/packages/client/core/src/mock/data/showcase.ts
@@ -126,7 +126,7 @@ export const SHOWCASE_PLAN: Plan = {
export const SHOWCASE_PERMISSION_DIFF: Extract = {
type: 'diff',
- path: 'packages/client/workbench/src/mock/dev-mock-transport.ts',
+ path: 'packages/client/core/src/mock/dev-mock-transport.ts',
oldText: "const coverage = 'thin';\n",
newText: "const coverage = 'rich';\n",
};
@@ -271,7 +271,7 @@ export const SHOWCASE_STREAM_START_DELAY_MS = 1000;
export const SHOWCASE_STREAM_CHUNK_LATENCY_MS = 220;
export const SHOWCASE_TERMINAL_START_OUTPUT =
- '$ pnpm vitest run packages/client/workbench/src/mock\n' +
+ '$ pnpm vitest run packages/client/core/src/mock\n' +
'\u{1B}[32mPASS\u{1B}[0m dev mock transport (4)\n' +
'\u{1B}[33mWARN\u{1B}[0m retry fixture exercised\n' +
'\u{1B}[31mFAIL\u{1B}[0m expected negative-path fixture\n';
@@ -552,7 +552,7 @@ export function createShowcaseToolBursts(terminalId = SHOWCASE_TERMINAL_ID): Sho
kind: 'execute',
status: 'completed',
content: [{ type: 'terminal', terminalId }],
- rawInput: { command: 'pnpm vitest run packages/client/workbench/src/mock' },
+ rawInput: { command: 'pnpm vitest run packages/client/core/src/mock' },
},
{
toolCallId: 'mock-tool-execute-lint',
@@ -781,11 +781,11 @@ export function createShowcaseToolBursts(terminalId = SHOWCASE_TERMINAL_ID): Sho
title: 'Move activity fixture',
kind: 'move',
status: 'completed',
- locations: [{ path: 'packages/client/workbench/src/mock/data/showcase.ts' }],
+ locations: [{ path: 'packages/client/core/src/mock/data/showcase.ts' }],
content: [],
rawInput: {
- path: 'packages/client/workbench/src/mock/data/activity.ts',
- move_path: 'packages/client/workbench/src/mock/data/showcase.ts',
+ path: 'packages/client/core/src/mock/data/activity.ts',
+ move_path: 'packages/client/core/src/mock/data/showcase.ts',
},
},
{
diff --git a/packages/client/workbench/src/mock/dev-mock-host.ts b/packages/client/core/src/mock/dev-mock-host.ts
similarity index 99%
rename from packages/client/workbench/src/mock/dev-mock-host.ts
rename to packages/client/core/src/mock/dev-mock-host.ts
index dd9155e0c..d7e48564c 100644
--- a/packages/client/workbench/src/mock/dev-mock-host.ts
+++ b/packages/client/core/src/mock/dev-mock-host.ts
@@ -561,7 +561,7 @@ export class DevMockHost {
? {
status: 'conflict',
files: [
- { path: 'packages/client/workbench/src/mock.ts', additions: 12, deletions: 4 },
+ { path: 'packages/client/core/src/mock.ts', additions: 12, deletions: 4 },
{ path: 'packages/presentation/ui/src/shell.tsx', additions: 3, deletions: 1 },
],
}
diff --git a/packages/client/workbench/src/mock/dev-mock-transport.ts b/packages/client/core/src/mock/dev-mock-transport.ts
similarity index 100%
rename from packages/client/workbench/src/mock/dev-mock-transport.ts
rename to packages/client/core/src/mock/dev-mock-transport.ts
diff --git a/packages/client/workbench/tests/integration/dev-mock-transport.test.ts b/packages/client/core/tests/integration/dev-mock-transport.test.ts
similarity index 99%
rename from packages/client/workbench/tests/integration/dev-mock-transport.test.ts
rename to packages/client/core/tests/integration/dev-mock-transport.test.ts
index 3febd842e..2540b2b97 100644
--- a/packages/client/workbench/tests/integration/dev-mock-transport.test.ts
+++ b/packages/client/core/tests/integration/dev-mock-transport.test.ts
@@ -1,4 +1,3 @@
-import { LinkCodeClient } from '@linkcode/client-core';
import type {
Accounts,
AgentEvent,
@@ -10,6 +9,7 @@ import type {
import { nullthrow } from 'foxts/guard';
import { wait } from 'foxts/wait';
import { describe, expect, it } from 'vitest';
+import { LinkCodeClient } from '../../src';
import { createDevMockTransport } from '../../src/mock/dev-mock-transport';
const rBlobUrl = /^blob:/;
@@ -636,7 +636,7 @@ describe('dev mock transport', () => {
true,
);
expect(tools.some((tool) => tool.status === 'failed')).toBe(true);
- expect(terminalOutput).toContain('pnpm vitest run packages/client/workbench/src/mock');
+ expect(terminalOutput).toContain('pnpm vitest run packages/client/core/src/mock');
expect(terminalOutput).toContain('mock terminal stream finished');
const streamChunks = events.filter(
diff --git a/packages/client/workbench/src/runtime/daemon-transport.ts b/packages/client/workbench/src/runtime/daemon-transport.ts
index 50d7d2a59..e8046e62f 100644
--- a/packages/client/workbench/src/runtime/daemon-transport.ts
+++ b/packages/client/workbench/src/runtime/daemon-transport.ts
@@ -1,6 +1,6 @@
+import { createDevMockTransport } from '@linkcode/client-core/mock';
import type { Transport } from '@linkcode/transport';
import { SocketIoTransport } from '@linkcode/transport';
-import { createDevMockTransport } from '../mock/dev-mock-transport';
/**
* Construct the transport the workbench data plane rides on. `--mode mock` (dev only) swaps the
diff --git a/packages/presentation/i18n/src/locales/en.ts b/packages/presentation/i18n/src/locales/en.ts
index 7ca194b4b..58d591fff 100644
--- a/packages/presentation/i18n/src/locales/en.ts
+++ b/packages/presentation/i18n/src/locales/en.ts
@@ -1407,6 +1407,8 @@ export const en = {
},
chat: {
allowTitle: 'Allow "{title}"?',
+ answer: 'Answer',
+ close: 'Close',
skip: 'Skip',
next: 'Next',
submitAnswers: 'Submit answers',
@@ -1419,7 +1421,6 @@ export const en = {
cancel: 'Cancel',
},
connection: {
- connecting: 'Connecting to the host…',
unavailableTitle: 'Host unavailable',
error: 'Unable to reach the host at {url}.',
retry: 'Retry',
diff --git a/packages/presentation/i18n/src/locales/zh-cn.ts b/packages/presentation/i18n/src/locales/zh-cn.ts
index badd487a0..60ec1ca2d 100644
--- a/packages/presentation/i18n/src/locales/zh-cn.ts
+++ b/packages/presentation/i18n/src/locales/zh-cn.ts
@@ -1362,6 +1362,8 @@ export const zhCN = {
compactedTokens: '{pre} → {post} tokens',
},
chat: {
+ answer: '回答',
+ close: '关闭',
allowTitle: '允许「{title}」?',
skip: '跳过',
next: '下一步',
@@ -1375,7 +1377,6 @@ export const zhCN = {
cancel: '取消',
},
connection: {
- connecting: '正在连接 host…',
unavailableTitle: '无法连接 host',
error: '无法连接 host({url})。',
retry: '重试',
diff --git a/packages/presentation/ui/src/chat/question-answer.ts b/packages/presentation/ui/src/chat/question-answer.ts
new file mode 100644
index 000000000..89af12806
--- /dev/null
+++ b/packages/presentation/ui/src/chat/question-answer.ts
@@ -0,0 +1,8 @@
+import type { Question, QuestionAnswer } from '@linkcode/schema';
+
+export function answerText(question: Question, answer: QuestionAnswer): string | undefined {
+ const labelByOption = new Map(question.options.map((option) => [option.optionId, option.label]));
+ const parts = answer.selectedOptionIds.map((optionId) => labelByOption.get(optionId) ?? optionId);
+ if (answer.customText) parts.push(answer.customText);
+ return parts.length > 0 ? parts.join(', ') : undefined;
+}
diff --git a/packages/presentation/ui/src/chat/question-call-item.tsx b/packages/presentation/ui/src/chat/question-call-item.tsx
index 01b4f28af..db94c2aa4 100644
--- a/packages/presentation/ui/src/chat/question-call-item.tsx
+++ b/packages/presentation/ui/src/chat/question-call-item.tsx
@@ -1,7 +1,8 @@
-import type { Question, QuestionAnswer, ToolCall } from '@linkcode/schema';
+import type { ToolCall } from '@linkcode/schema';
import { MessageCircleQuestionMarkIcon } from 'lucide-react';
import { useTranslations } from 'use-intl';
import type { QuestionConversationItem } from './conversation-prompts';
+import { answerText } from './question-answer';
import { Tool, ToolContent, ToolHeader } from './tool';
/** Timeline record of an agent question ask. The dock's QuestionPrompt is the actionable
@@ -66,11 +67,3 @@ export function QuestionCallItem({
);
}
-
-/** Chosen option labels plus any custom text; undefined when the question was skipped. */
-function answerText(question: Question, answer: QuestionAnswer): string | undefined {
- const labelByOption = new Map(question.options.map((option) => [option.optionId, option.label]));
- const parts = answer.selectedOptionIds.map((optionId) => labelByOption.get(optionId) ?? optionId);
- if (answer.customText) parts.push(answer.customText);
- return parts.length > 0 ? parts.join(', ') : undefined;
-}
diff --git a/packages/presentation/ui/src/native/agent-icon.tsx b/packages/presentation/ui/src/native/agent-icon.tsx
index 9c5886b5a..462ecf018 100644
--- a/packages/presentation/ui/src/native/agent-icon.tsx
+++ b/packages/presentation/ui/src/native/agent-icon.tsx
@@ -1,5 +1,6 @@
import type { AgentKind } from '@linkcode/schema';
import { useThemeColor } from 'heroui-native';
+import type { ColorValue } from 'react-native';
import { Text, View } from 'react-native';
import Svg, { Path } from 'react-native-svg';
import { AGENT_INITIALS } from '../agent-meta';
@@ -24,7 +25,7 @@ export interface AgentIconProps {
/** Outer box size in dp (the glyph scales inside it). */
size?: number;
/** Glyph tint override; defaults to accent-foreground (solid) / foreground (ghost). */
- color?: string;
+ color?: ColorValue;
}
/** Agent brand chip — the native counterpart of chat/agent-icon.tsx. */
diff --git a/packages/presentation/ui/src/native/index.ts b/packages/presentation/ui/src/native/index.ts
index 59233841b..af27e2628 100644
--- a/packages/presentation/ui/src/native/index.ts
+++ b/packages/presentation/ui/src/native/index.ts
@@ -5,7 +5,12 @@ export type { ModelOption } from '../agent-models';
export { groupModelsByProvider, modelChoiceKey, resolveModel } from '../agent-models';
export { stripAnsi } from '../ansi';
export type { CurrentPlan, PromptConversationItem } from '../chat/conversation-prompts';
-export { selectCurrentPlan, selectPendingPromptItems } from '../chat/conversation-prompts';
+export {
+ conversationFlowItems,
+ selectCurrentPlan,
+ selectPendingPromptItems,
+} from '../chat/conversation-prompts';
+export { answerText } from '../chat/question-answer';
export { diffLines, patchLines } from '../diff-utils';
export { repositoryLabel } from '../repository-label';
export * from '../thread-groups';
diff --git a/packages/presentation/ui/src/native/markdown/code-block.tsx b/packages/presentation/ui/src/native/markdown/code-block.tsx
index 134175415..48c707972 100644
--- a/packages/presentation/ui/src/native/markdown/code-block.tsx
+++ b/packages/presentation/ui/src/native/markdown/code-block.tsx
@@ -3,8 +3,9 @@ import { noop } from 'foxact/noop';
import { useEffect } from 'foxact/use-abortable-effect';
import { useState } from 'react';
import { ScrollView, Text, useColorScheme, View } from 'react-native';
+import { MONO_FONT_FAMILY } from './mono-font';
-const CODE_FONT = { fontFamily: 'Menlo', fontSize: 12, lineHeight: 18 } as const;
+const CODE_FONT = { fontFamily: MONO_FONT_FAMILY, fontSize: 12, lineHeight: 18 } as const;
interface HighlightedCode {
code: string;
diff --git a/packages/presentation/ui/src/native/markdown/markdown.tsx b/packages/presentation/ui/src/native/markdown/markdown.tsx
index 6bbae22c4..cf59a26df 100644
--- a/packages/presentation/ui/src/native/markdown/markdown.tsx
+++ b/packages/presentation/ui/src/native/markdown/markdown.tsx
@@ -3,9 +3,10 @@
import type { ListItem, PhrasingContent, RootContent } from 'mdast';
import { Linking, Text, View } from 'react-native';
import { CodeBlock } from './code-block';
+import { MONO_FONT_FAMILY } from './mono-font';
import { parseMarkdown } from './parse';
-const MONO = { fontFamily: 'Menlo', fontSize: 13 } as const;
+const MONO = { fontFamily: MONO_FONT_FAMILY, fontSize: 13 } as const;
function renderInline(nodes: PhrasingContent[]): React.ReactNode {
return nodes.map((node, key) => {
diff --git a/packages/presentation/ui/src/native/markdown/mono-font.ts b/packages/presentation/ui/src/native/markdown/mono-font.ts
new file mode 100644
index 000000000..12185bbca
--- /dev/null
+++ b/packages/presentation/ui/src/native/markdown/mono-font.ts
@@ -0,0 +1,5 @@
+import { Platform } from 'react-native';
+
+/** Menlo is an Apple font; without the select Android silently falls back to proportional sans
+ * and code loses column alignment. */
+export const MONO_FONT_FAMILY = Platform.select({ ios: 'Menlo', default: 'monospace' });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 9fcb0df18..5a593f200 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -442,13 +442,13 @@ importers:
dependencies:
'@better-auth/expo':
specifier: 1.7.0-rc.2
- version: 1.7.0-rc.2(5705381685e3aee0fa9e57d39f706f20)
+ version: 1.7.0-rc.2(3e673fc1d89bb5bb2e0be4405d007f02)
'@expo/ui':
- specifier: 57.0.7
- version: 57.0.7(@babel/core@7.29.7(supports-color@8.1.1))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ specifier: 57.0.11
+ version: 57.0.11(@babel/core@7.29.7(supports-color@8.1.1))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
'@gorhom/bottom-sheet':
specifier: ^5.2.14
- version: 5.2.14(6e44dcb7dcfecf271936e923703300f2)
+ version: 5.2.14(b3053a3c63c272f26bedf4b267721ee5)
'@linkcode/client-core':
specifier: workspace:*
version: link:../../packages/client/core
@@ -472,97 +472,100 @@ importers:
version: link:../../packages/presentation/ui
'@react-native-community/netinfo':
specifier: 12.0.1
- version: 12.0.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 12.0.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
'@sentry/react-native':
specifier: ~7.11.0
- version: 7.11.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ version: 7.11.0(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
better-auth:
specifier: 1.7.0-rc.2
version: 1.7.0-rc.2(@opentelemetry/api@1.9.1)(better-sqlite3@12.11.1)(drizzle-kit@0.31.10)(drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(better-sqlite3@12.11.1)(kysely@0.29.4))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(vitest@4.1.10(@opentelemetry/api@1.9.1)(@types/node@26.1.1)(happy-dom@20.10.6)(jsdom@29.1.1(@noble/hashes@2.2.0))(vite@8.1.5(@types/node@26.1.1)(esbuild@0.28.1)(jiti@2.7.0)(terser@5.48.0)(tsx@4.23.1)(yaml@2.9.0)))
expo:
- specifier: ~57.0.4
- version: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ specifier: ~57.0.14
+ version: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
expo-apple-authentication:
specifier: ~57.0.1
- version: 57.0.1(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
expo-audio:
- specifier: ~57.0.0
- version: 57.0.0(expo-asset@57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ specifier: ~57.0.3
+ version: 57.0.4(expo-asset@57.0.16(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
expo-background-task:
- specifier: ~57.0.2
- version: 57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ specifier: ~57.0.11
+ version: 57.0.16(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
expo-camera:
- specifier: ~57.0.1
- version: 57.0.1(@types/emscripten@1.41.5)(expo@57.0.4)(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ specifier: ~57.0.3
+ version: 57.0.4(@types/emscripten@1.41.5)(expo@57.0.20)(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
expo-clipboard:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ specifier: ~57.0.1
+ version: 57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
expo-constants:
- specifier: ~57.0.3
- version: 57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
+ specifier: ~57.0.12
+ version: 57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
expo-crypto:
specifier: ~57.0.1
- version: 57.0.1(expo@57.0.4)
+ version: 57.0.1(expo@57.0.20)
expo-dev-client:
- specifier: ~57.0.5
- version: 57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ specifier: ~57.0.13
+ version: 57.0.18(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
expo-device:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)
+ specifier: ~57.0.1
+ version: 57.0.1(expo@57.0.20)
expo-document-picker:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)
+ specifier: ~57.0.1
+ version: 57.0.1(expo@57.0.20)
expo-file-system:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ specifier: ~57.0.4
+ version: 57.0.6(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
expo-font:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ specifier: ~57.0.1
+ version: 57.0.3(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
expo-haptics:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)
+ specifier: ~57.0.1
+ version: 57.0.2(expo@57.0.20)
expo-image-picker:
- specifier: ~57.0.2
- version: 57.0.2(expo@57.0.4)
+ specifier: ~57.0.11
+ version: 57.0.16(expo@57.0.20)
expo-libghostty:
specifier: ^0.8.1
- version: 0.8.1(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 0.8.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
expo-linking:
- specifier: ~57.0.2
- version: 57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ specifier: ~57.0.6
+ version: 57.0.9(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
expo-local-authentication:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)
+ specifier: ~57.0.2
+ version: 57.0.2(expo@57.0.20)
expo-network:
specifier: ~57.0.1
- version: 57.0.1(expo@57.0.4)(react@19.2.3)
+ version: 57.0.1(expo@57.0.20)(react@19.2.3)
expo-notifications:
- specifier: ~57.0.3
- version: 57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ specifier: ~57.0.12
+ version: 57.0.17(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
expo-router:
- specifier: ~57.0.4
- version: 57.0.4(3bc51b20989ed2c3020a920bb0eeb449)
+ specifier: ~57.0.14
+ version: 57.0.19(c021d0cd4acfce308650ff4c6746808d)
expo-secure-store:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)
+ specifier: ~57.0.1
+ version: 57.0.3(expo@57.0.20)
expo-sharing:
- specifier: ~57.0.3
- version: 57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ specifier: ~57.0.13
+ version: 57.0.18(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
expo-sqlite:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ specifier: ~57.0.1
+ version: 57.0.2(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
expo-status-bar:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- expo-task-manager:
+ specifier: ~57.0.1
+ version: 57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo-symbols:
specifier: ~57.0.2
- version: 57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ version: 57.0.2(expo-font@57.0.3)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo-task-manager:
+ specifier: ~57.0.11
+ version: 57.0.16(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
expo-updates:
- specifier: ~57.0.6
- version: 57.0.6(expo-dev-client@57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)))(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ specifier: ~57.0.15
+ version: 57.0.21(expo-dev-client@57.0.18(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)))(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
expo-web-browser:
- specifier: ~57.0.0
- version: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ specifier: ~57.0.2
+ version: 57.0.2(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
foxact:
specifier: ^0.3.10
version: 0.3.10(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -571,13 +574,13 @@ importers:
version: 5.9.1
heroui-native:
specifier: 1.0.6
- version: 1.0.6(165eb86db294c7d42ce40e8c9ab66801)
+ version: 1.0.6(5dab09f39b7c1f66cb0e1b4de78ecc64)
lucide-react-native:
specifier: ^1.25.0
- version: 1.27.0(react-native-svg@15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 1.27.0(react-native-svg@15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
posthog-react-native:
specifier: ^4.60.0
- version: 4.60.0(expo-application@57.0.2(expo@57.0.4))(expo-device@57.0.0(expo@57.0.4))(expo-file-system@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)))(expo-localization@57.0.1(expo@57.0.4)(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))
+ version: 4.60.0(expo-application@57.0.2(expo@57.0.20))(expo-device@57.0.1(expo@57.0.20))(expo-file-system@57.0.6(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)))(expo-localization@57.0.1(expo@57.0.20)(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))
react:
specifier: 19.2.3
version: 19.2.3
@@ -585,35 +588,35 @@ importers:
specifier: 19.2.3
version: 19.2.3(react@19.2.3)
react-native:
- specifier: 0.86.0
- version: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ specifier: 0.86.2
+ version: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
react-native-gesture-handler:
specifier: ~2.32.0
- version: 2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-native-keyboard-controller:
specifier: 1.21.9
- version: 1.21.9(react-native-reanimated@4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 1.21.9(react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-native-reanimated:
- specifier: 4.5.0
- version: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ specifier: 4.5.1
+ version: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-native-safe-area-context:
specifier: ~5.7.0
- version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-native-screens:
- specifier: 4.25.2
- version: 4.25.2(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ specifier: 4.26.2
+ version: 4.26.2(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-native-svg:
specifier: 15.15.4
- version: 15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-native-web:
specifier: ^0.21.2
version: 0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
react-native-webview:
specifier: 13.16.1
- version: 13.16.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ version: 13.16.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-native-worklets:
- specifier: 0.10.0
- version: 0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ specifier: 0.10.1
+ version: 0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
tailwind-merge:
specifier: ^3.6.0
version: 3.6.0
@@ -622,7 +625,7 @@ importers:
version: 3.3.0(tailwind-merge@3.6.0)(tailwindcss@4.3.3)
uniwind:
specifier: 1.10.0
- version: 1.10.0(@expo/metro-config@57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(supports-color@8.1.1))(metro-cache@0.84.4(supports-color@8.1.1))(metro-transform-worker@0.84.4(supports-color@8.1.1))(metro@0.84.4(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(tailwindcss@4.3.3)
+ version: 1.10.0(@expo/metro-config@57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.20)(supports-color@8.1.1))(metro-cache@0.84.5(supports-color@8.1.1))(metro-transform-worker@0.84.5(supports-color@8.1.1))(metro@0.84.5(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(tailwindcss@4.3.3)
use-intl:
specifier: 'catalog:'
version: 4.13.4(react@19.2.3)
@@ -635,7 +638,7 @@ importers:
devDependencies:
'@bacons/apple-targets':
specifier: 4.0.7
- version: 4.0.7(expo@57.0.4)(supports-color@8.1.1)
+ version: 4.0.7(expo@57.0.20)(supports-color@8.1.1)
'@types/react':
specifier: 'catalog:'
version: 19.2.17
@@ -1265,10 +1268,10 @@ importers:
version: 9.3.0
react-native:
specifier: '>=0.80'
- version: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ version: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
react-native-safe-area-context:
specifier: '>=5.0.0'
- version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ version: 5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
rehype-slug:
specifier: ^6.0.0
version: 6.0.0
@@ -1326,7 +1329,7 @@ importers:
version: 19.2.3(@types/react@19.2.17)
heroui-native:
specifier: ^1.0.6
- version: 1.0.6(63282ea4a5d8a79f137ba1bf529bfa14)
+ version: 1.0.6(759903ba85becadd460e21d1d0660b91)
react:
specifier: 'catalog:'
version: 19.3.0-canary-96fcba90-20260728
@@ -1335,7 +1338,7 @@ importers:
version: 19.3.0-canary-96fcba90-20260728(react@19.3.0-canary-96fcba90-20260728)
react-native-svg:
specifier: ^15.15.4
- version: 15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ version: 15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
typescript:
specifier: 'catalog:'
version: '@typescript/typescript6@6.0.2'
@@ -1398,9 +1401,6 @@ importers:
packages:
- '@adobe/css-tools@4.5.0':
- resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==}
-
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
@@ -2874,8 +2874,8 @@ packages:
'@expo-google-fonts/material-symbols@0.4.38':
resolution: {integrity: sha512-IJkBtN1o8u9BW5fvSii1MyHPQ7Q0HxbWcVBvOrOzgMLpVtZw7R2w94wBTVR7kZwv3w1JNTESMmLA5Sqn1+Z36A==}
- '@expo/cli@57.0.6':
- resolution: {integrity: sha512-14wEb2e8lctlxGARbinUEr+9EmrTR2jIgcaPBBkZXDFMRJmdoY0ic18JuFJPi+7CuQ4XWiGK6obN4VK2PXosLA==}
+ '@expo/cli@57.0.22':
+ resolution: {integrity: sha512-MdToR0ZZ1yjfkqbP1jcA7viS9duMPGq/C+qQ/21dtq+en583Ghp+O11BP/wujHa9ZGRtXimzuKPZDpPresVE0g==}
hasBin: true
peerDependencies:
expo: '*'
@@ -2890,20 +2890,20 @@ packages:
'@expo/code-signing-certificates@0.0.6':
resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==}
- '@expo/config-plugins@57.0.3':
- resolution: {integrity: sha512-J3P2T7FoOE9P3TuLcJXwt8jISKRxo7UntTzbJ/Qc5F7QXenNntk/4t1XndVkLucYjpnHArPd9xzDXuxxKEHVbQ==}
+ '@expo/config-plugins@57.0.9':
+ resolution: {integrity: sha512-hHgfL1avkCdEvDSw7IwlKwRYYNgcxzbNNMIk6W6lTkJpY0MajinAfeJUS0J+wPCsjUfGbVqOJM+XhPaO5ulUxg==}
- '@expo/config-types@57.0.1':
- resolution: {integrity: sha512-fo7d/Ym28uwGzdTV2leEvpsb9t+7i8YHjy471m31+cmDt4BRd/l7e94JHyrXAq4SWOBVus16S0JLqm89SRCCdw==}
+ '@expo/config-types@57.0.2':
+ resolution: {integrity: sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g==}
- '@expo/config@57.0.3':
- resolution: {integrity: sha512-IshqjjpGtT7wj86pxgsfMD1/abNMfu1wZ07ubyYO29l+PWa0eAh2TcpyLcyBaqo01IonF6646xWwrCPcdlUl3Q==}
+ '@expo/config@57.0.9':
+ resolution: {integrity: sha512-dmzlKraIFxa7wLwV6K7WzI8jp6QZpW6Mc5mGjLimJUFjzh4uQdYaT3m3plEutM5yxBoBEwqzks7l+I/ljCbxAQ==}
'@expo/devcert@1.2.1':
resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==}
- '@expo/devtools@57.0.0':
- resolution: {integrity: sha512-i8ITQmf/wB0JNQ2gASFOKvqo1zRWCl/VfPlFRXdz6UkHen4s31NmMy0zmumS+pMpwn0+gA6oU4FF4zRDRG488Q==}
+ '@expo/devtools@57.0.1':
+ resolution: {integrity: sha512-GyUf+wFNkbttaX0jR7MZa9bm77U0IrLg6d2AjpxdyoXw/w4abHoXG0oFufwLMgP9zLTd5+Ct4X/ffNUTnlzZgg==}
peerDependencies:
react: '*'
react-native: '*'
@@ -2913,59 +2913,59 @@ packages:
react-native:
optional: true
- '@expo/dom-webview@57.0.0':
- resolution: {integrity: sha512-zBZw52KnaHf9zGVp1eJk8kNfc+5ArGf+KvTL3SeMsr03K6tPJtLbgycVLjrAiLMfhzqGVjPD2G+rbNwpbLUxcg==}
+ '@expo/dom-webview@57.0.1':
+ resolution: {integrity: sha512-lAKsME4SAq+8sf56oN0DX5TBYyruupoRxbWbD2xf9RnKY8y6x8eb9LCE5pxSN0qyWdqnp+0wmyWzDkKboThKAw==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
- '@expo/env@2.4.1':
- resolution: {integrity: sha512-3c9Mg9x0HmGPEsVrGAGyEDJsNUOZ55cZvZ47/HLmXh7MHV9Zv7My73wThklKrObaBBoMfE4YqpKjYKDRzojpjQ==}
+ '@expo/env@2.4.3':
+ resolution: {integrity: sha512-M1NXeZCA1mkMkYOyIe7PlyRX0/jqFtMoJgyblnlq/vpCRfmueFT7RnGSQG8uEFDF5WHOFGijAQ3fogPh3/n5Ng==}
engines: {node: '>=20.12.0'}
- '@expo/expo-modules-macros-plugin@0.3.0':
- resolution: {integrity: sha512-2tRq8kiIZTVZcI5uggh86HefQ7s++Zk5WkFFomNp4aUqyN5ownHHvj1jPEP9jWXaXjPDmWuf5SUZTGD5G6AKkg==}
+ '@expo/expo-modules-macros-plugin@0.6.1':
+ resolution: {integrity: sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA==}
- '@expo/fingerprint@0.20.3':
- resolution: {integrity: sha512-WDV2hS87C61TsX55w4A0cPnbnN8bqdVHp2MpZsJTvB4Tv4TITgSxWYhRj/xv0pwcZvKHn05MBg6AfHPNj9o5Sw==}
+ '@expo/fingerprint@0.20.12':
+ resolution: {integrity: sha512-FIR5fkZYeFaLSowmjgyB6RPKl8AXeE8HuCBHHvyxK8UhOjpPfCAmzT4E7v2yub4qqDafKnfcOFCYxvpUEu+01w==}
hasBin: true
- '@expo/image-utils@0.11.1':
- resolution: {integrity: sha512-0JueH4vdgAZQmhlSYFvTQzt4b4NO5cnByDuApw7bMUIjhwLRnT46Ki3ritMrzJMQaO2lLK2flInZbsZbOuy8nw==}
+ '@expo/image-utils@0.11.5':
+ resolution: {integrity: sha512-KPQBTpmpAfy/Vu9y4wPW808/qtZxjYmyJg8cm2QCPAupp+qEWA3b5zmk0ulOwQ9OgeHxuCPgUqWgkwHFo7UsrQ==}
- '@expo/inline-modules@0.1.2':
- resolution: {integrity: sha512-wc5wH4ND647Ne/6A/ESuelcqOQUEvK8MYjjvbp5wcl59dECk7juh0cFOJjAWkpc4pLTcvqXBSRcUDpGWm5XNWg==}
+ '@expo/inline-modules@0.1.7':
+ resolution: {integrity: sha512-Bz/khd1gIJqDkje7t5ejD5e9jFbm4xEJzWSwRKadRo6gruepbw1xJ3Eb+e58OS8fY1ZNQYjzZbspnuph67sN0g==}
- '@expo/json-file@11.0.0':
- resolution: {integrity: sha512-pHJCETqFL5x5BzNV6cEPwjwuECgGmnl0bNmfHIJ6LM1tlh2eVXi5HEdit3zby/JO/B8Otk5cgcqtJXgvvUat3A==}
+ '@expo/json-file@11.0.1':
+ resolution: {integrity: sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==}
- '@expo/local-build-cache-provider@57.0.2':
- resolution: {integrity: sha512-/8/FoYrEBJZPG4wR8kO6REI4VRCPAStD+7wS/Ds6XXddxhmyMP3pvUJz/c3icyAoqpbqeZZkR6E1ptT5Gi5iXg==}
+ '@expo/local-build-cache-provider@57.0.8':
+ resolution: {integrity: sha512-SEdE0pAQrr90bRh3MNR0ZuwoIBw390cYdFgbn7Vk0Mtm9EHaBfP7kYj+2hXnXZJgOEm3/JMtfoD3rV7rWA9FGg==}
- '@expo/log-box@57.0.0':
- resolution: {integrity: sha512-tt9x76IEE8hp2FWTLPfEArrTyD7GCoUe3TpohESy+SPZ/OqkUnSXz40xnUuE0XbE132z8c5CRCfXQLM9DTEknQ==}
+ '@expo/log-box@57.0.4':
+ resolution: {integrity: sha512-IxwS9s1L2muj8mj8AQSuiy7u8OFJdc02NRFo2me/Tj6DiaeG5SREqmpBE4rQpR2cadqSg5jl8Qab8Cjie616dg==}
peerDependencies:
- '@expo/dom-webview': ^57.0.0
+ '@expo/dom-webview': ^57.0.1
expo: '*'
react: '*'
react-native: '*'
- '@expo/metro-config@57.0.3':
- resolution: {integrity: sha512-k7qcjTe1xDrUW15Ue86WAsJSL0ubiSluBXVNRFoe9snAWhMzBGzdZfl9XiDf4TNUV43T0L1ch+n4GmDqamvfEg==}
+ '@expo/metro-config@57.0.12':
+ resolution: {integrity: sha512-S62Lrq35HZqBFD55423pmWb8PjaiR/W02zQC1uECBmw1vTN8WZaFz4TJ0i21EeJzwfebMb9MLxL8JZ102Z6VbA==}
peerDependencies:
expo: '*'
peerDependenciesMeta:
expo:
optional: true
- '@expo/metro-file-map@57.0.0':
- resolution: {integrity: sha512-/sxwKQmwpYFYjFT6VYFpgldTwSv53OCx4/UCpLCv9iqQLVQ30hFEJsxGA9Jif8n9pacigK4P5/0ZJ2zMHm6arA==}
+ '@expo/metro-file-map@57.0.2':
+ resolution: {integrity: sha512-tb50nSIWwKpRufSkGuivOK0FbUxv1Uwqptb0SzFTk0bkmYmcy3gIwnCbOHTfmrQDVndRhPEiu2SYgh9Z0PCvGg==}
- '@expo/metro-runtime@57.0.3':
- resolution: {integrity: sha512-FMXIpvPIlAvJlaQmlmP79gNt7KS5DyruKSOkOW1DTkXnJOiXQnzvebqPLOEYH71fbS1emJ3Dq45lDz7+sryVbA==}
+ '@expo/metro-runtime@57.0.15':
+ resolution: {integrity: sha512-9RaFkqIzRud6ol9fc5ZKHgNDYeDjQVBdAQC+rjSQ6RiKV0pY+SjDqGmKY5a4M9TaV0qLMCNNrp5R88BqC2xbuQ==}
peerDependencies:
- '@expo/log-box': ^57.0.0
+ '@expo/log-box': ^57.0.4
expo: '*'
react: '*'
react-dom: '*'
@@ -2974,42 +2974,42 @@ packages:
react-dom:
optional: true
- '@expo/metro@56.0.0':
- resolution: {integrity: sha512-5gIgQHtEpjjvsjKfVtIv23a98LLRV0/y07PDShEwYSytAMlE3FSF8RHXqtHc1sUJL6dn7hnuIBpIbrLXXuVi0A==}
+ '@expo/metro@56.0.2':
+ resolution: {integrity: sha512-Ld5AeYMCCDa8bLeWhfuLbZFFjlV3f6ORqyPz2glGh6RltIngMuLf9BTC2yvHFjkKuGxL5SynijmA8xmNNWn5iA==}
- '@expo/osascript@2.7.0':
- resolution: {integrity: sha512-wKIXL8UtbuX4KwavPasIW3CUcgTbYfjzLcgUhjyKUAYDEqMaf6gmU1bqz3ffBPTokmX+G8/vFG1ZuI9etQWukA==}
+ '@expo/osascript@2.7.1':
+ resolution: {integrity: sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==}
engines: {node: '>=12'}
- '@expo/package-manager@1.13.0':
- resolution: {integrity: sha512-s3W3eZafJDEyVL7W/jxj2Nz3eONKxSCU604S5xj8ijrVaRz83x0DnZznLf/UXQEI1w+FyibH68nHeQyk767b1A==}
+ '@expo/package-manager@1.13.1':
+ resolution: {integrity: sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg==}
'@expo/plist@0.0.18':
resolution: {integrity: sha512-+48gRqUiz65R21CZ/IXa7RNBXgAI/uPSdvJqoN9x1hfL44DNbUoWHgHiEXTx7XelcATpDwNTz6sHLfy0iNqf+w==}
- '@expo/plist@0.8.0':
- resolution: {integrity: sha512-24JlUJI4PwHN4PLydlzFEzCdiqybfaV5t04QBkOg8em3AjvHKbMgBGlKreiuOoc0rNa3DZ21ZqL+xLGMBLQNKQ==}
+ '@expo/plist@0.8.1':
+ resolution: {integrity: sha512-3gTReGIUm0oRaMClsAJYxBnVPCl6fVpsl8HS+DTVxDhW4GyVyxg9E/Znm3BvcHtUJ51RJJI14pC1wvrNilCRHw==}
- '@expo/prebuild-config@57.0.5':
- resolution: {integrity: sha512-KnKx6Iu4jppbNVtt9yTrN10OVoPZlOYYyLZuarY70u6fgUO9lYKDa06Hxv+PmIwcnygfj2CSBBl5ewBqS2bxtw==}
+ '@expo/prebuild-config@57.0.15':
+ resolution: {integrity: sha512-xTbWHroj0PDmlbqvmU+zF9ZZxveJkiuyiPoeRJYRGruFHebRAWnoTdw5S7d/UCzDBI8ropGGu9g2eb2nMxtvAw==}
- '@expo/require-utils@57.0.1':
- resolution: {integrity: sha512-uXen5/4x7j60I5slShgZr5QEtJDBK8homFiNLDnDrNrxZhrRHXASo0H6JArs3/1PDzw1wahzhGWg2WKuYyZd0A==}
+ '@expo/require-utils@57.0.5':
+ resolution: {integrity: sha512-kTAXj9lDFEIPMsbAOGCGbjBbMF0oi7CqkYM79KOX0DDD9wSwXmlKL1z2h8OwsrBf7mbOo2DjlRvZu4BEjrIxGw==}
peerDependencies:
- typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0
+ typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0
peerDependenciesMeta:
typescript:
optional: true
- '@expo/router-server@57.0.2':
- resolution: {integrity: sha512-hu9qhnq2PKuwMXIoRMAa2+HUeqSut9HwiQf3a62tfGTIxPN3rKUZ10TozEEB+Pd/7WIa238sXMS5v1YhTuC9gA==}
+ '@expo/router-server@57.0.9':
+ resolution: {integrity: sha512-/PxRQozFesIyCJZOAtrQE8XcmcojNiL5ctPMQnbE4ojC2EJPu0zc7c0Y4PuXsoRxrZxm8Usw76/lCVrXcfTZ2w==}
peerDependencies:
- '@expo/metro-runtime': ^57.0.3
+ '@expo/metro-runtime': ^57.0.15
expo: '*'
- expo-constants: ^57.0.3
- expo-font: ^57.0.0
+ expo-constants: ^57.0.17
+ expo-font: ^57.0.3
expo-router: '*'
- expo-server: ^57.0.0
+ expo-server: ^57.0.3
react: '*'
react-dom: '*'
react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1
@@ -3023,8 +3023,8 @@ packages:
react-server-dom-webpack:
optional: true
- '@expo/schema-utils@57.0.1':
- resolution: {integrity: sha512-IVdaqtP3+iHFRE/y22mKijQtZanLcB18q1zi2kfN6RINTCryyzM7qHGsWHc5LBJbOuj1G4avCOECs97hOJm0GQ==}
+ '@expo/schema-utils@57.0.2':
+ resolution: {integrity: sha512-fMu/jyN0l1Wzv7XkeWR4IYCx1M8ryui3FdBNGrWwbRgJ7EhxXxK8E2jxP2W3pbgUwUY0V3hG8+GyfCZwny+Lxw==}
'@expo/sdk-runtime-versions@1.0.0':
resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==}
@@ -3036,8 +3036,25 @@ packages:
'@expo/sudo-prompt@9.3.2':
resolution: {integrity: sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==}
- '@expo/ui@57.0.7':
- resolution: {integrity: sha512-WqRVabl8VpHf3+YLHVjUy7PMIuXXI6DG88Vgmavro7Nd8Ks13h9sEJH/RLSCaJE2daVnqzEKY1v797BibuY9aw==}
+ '@expo/ui@57.0.11':
+ resolution: {integrity: sha512-m1BSq15sg4sCmoFwOsMxBpETgyFhGlCbVPUyjdJ6Y00Ve0Fb4sueaFLRyeYRbZ8DC1z3Hgp5LNCYzBBBdMhxMg==}
+ peerDependencies:
+ '@babel/core': '*'
+ expo: '*'
+ react: '*'
+ react-dom: '*'
+ react-native: '*'
+ react-native-worklets: '*'
+ peerDependenciesMeta:
+ '@babel/core':
+ optional: true
+ react-dom:
+ optional: true
+ react-native-worklets:
+ optional: true
+
+ '@expo/ui@57.0.16':
+ resolution: {integrity: sha512-3MK74dEPE4mwlv0VbyQKepmHigOs2bcOvay2SFqc1/QSywVTOjbkNPYUbq2H+LyOLP2sHMazrPBwfXGiYn3q7A==}
peerDependencies:
'@babel/core': '*'
expo: '*'
@@ -4264,66 +4281,88 @@ packages:
react: '>=16'
react-native: '>=0.57'
- '@react-native/assets-registry@0.86.0':
- resolution: {integrity: sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==}
+ '@react-native/assets-registry@0.86.2':
+ resolution: {integrity: sha512-vcX/mBjWAVnWofu7KecotquI2unZ/tITwA7OGdq/mdY/zmGXIEvYhfEYyOQij/LRqi9WAL+iizInTBWnxDhK/Q==}
+ engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
+
+ '@react-native/babel-plugin-codegen@0.86.2':
+ resolution: {integrity: sha512-NNDZqOlNbH5SzgPks1jFDYH3234Rpa5e/nhZymxhIiBH3NcE3uD+rGj/HWXhH7nHF2ToGK6XbUpqy7nmJPeh+g==}
+ engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
+
+ '@react-native/babel-plugin-codegen@0.86.3':
+ resolution: {integrity: sha512-O6Xza4JBGPIU8J7YbKTyBoYL4thpy8jMW/oaLDWdAyOwYHKIjK47pAL5HUEbOe2bWz2PEKjbYRF2ApkJv1ottQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- '@react-native/babel-plugin-codegen@0.86.0':
- resolution: {integrity: sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA==}
+ '@react-native/babel-preset@0.86.2':
+ resolution: {integrity: sha512-4XKEJ6jKW9lXMB1O5o47gBoGgolde1fbX13gLW/erlcn+1ky+MHHo5UjuM3RWGdPJHIvIzekDDumSUHhB9x5iQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
+ peerDependencies:
+ '@babel/core': '*'
- '@react-native/babel-preset@0.86.0':
- resolution: {integrity: sha512-bYQcWiPySNvF4dns9Ls9gMmwgq66ohvM9Fwc/Kn8r85t66UNHxch3p1QwPiSorDelFauZwJbgo9+ReibTgvpbA==}
+ '@react-native/codegen@0.86.2':
+ resolution: {integrity: sha512-xKkudsahUJ1n//55g4fXk5BStVqqmZlz8HQveL45ZxcfDnwvhuYe2GymksQANFsSN+slvrarjrfq8kIxJzbceA==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
peerDependencies:
'@babel/core': '*'
- '@react-native/codegen@0.86.0':
- resolution: {integrity: sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==}
+ '@react-native/codegen@0.86.3':
+ resolution: {integrity: sha512-Ux4jHi0fh+bdtVEcL0gaPLbY56V+SvFUDl/8sRAE1jdb4k+o7fT/4Nc29yz4X+qfjstkSqObQTMBGhdzxH9JvA==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
peerDependencies:
'@babel/core': '*'
- '@react-native/community-cli-plugin@0.86.0':
- resolution: {integrity: sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==}
+ '@react-native/community-cli-plugin@0.86.2':
+ resolution: {integrity: sha512-YHXNKoM6Y/HjREySZ5arET2xgiHgg67r1MdwJB//MPJAJ0Xc5g0u6UHxY9VzsHO3Y07dre6s0BinYwjt1SEWvQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
peerDependencies:
'@react-native-community/cli': '*'
- '@react-native/metro-config': 0.86.0
+ '@react-native/metro-config': 0.86.2
peerDependenciesMeta:
'@react-native-community/cli':
optional: true
'@react-native/metro-config':
optional: true
- '@react-native/debugger-frontend@0.86.0':
- resolution: {integrity: sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==}
+ '@react-native/debugger-frontend@0.86.2':
+ resolution: {integrity: sha512-KGS1aV5F6cIqpnoIUhLBXyVzy1oAj8jBFGau6vX4Vy0HXRJN7p+68RU7x6NuyraHvQcR14ccMGT5TkFuNjQ4gA==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- '@react-native/debugger-shell@0.86.0':
- resolution: {integrity: sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==}
+ '@react-native/debugger-frontend@0.86.3':
+ resolution: {integrity: sha512-TQmeofQ0PcuylhhlleOeuzHYZfbrgm3gayXzowqUEzgRisTm1D40/J3ggqs7XkQi5HP5ZA3n8dHmKL9vIzPcsw==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- '@react-native/dev-middleware@0.86.0':
- resolution: {integrity: sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==}
+ '@react-native/debugger-shell@0.86.2':
+ resolution: {integrity: sha512-/TaVJ2+gGajZPJGrFaObUQmHmlaxAlfmOPZicl6pNKDUjzSgFMpcLkdTOExvb+USYTVdGX1XwxXyvjQdUO2bvg==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- '@react-native/gradle-plugin@0.86.0':
- resolution: {integrity: sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==}
+ '@react-native/debugger-shell@0.86.3':
+ resolution: {integrity: sha512-O4ds+J7xZfxkbih9T+cAGegBdvKSPKYJm/lDgC9CpEjFMkmzWTpVLU3Qsv9sqZuo58z+sGhIcJfPsCFFWHpqbQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- '@react-native/js-polyfills@0.86.0':
- resolution: {integrity: sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==}
+ '@react-native/dev-middleware@0.86.2':
+ resolution: {integrity: sha512-B7L0vKvg+IcEElT7Vpqh1xj5yJAqWUegjbP+bQRaorJMAYnv11GkliTnZV2AdTDfZQJWgOEx8i8LGkHkUg7bnA==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- '@react-native/metro-babel-transformer@0.86.0':
- resolution: {integrity: sha512-SjKej3E5qIahqo/G+rSOrmJUQM44RyKtWtO+VfmKAAMoJWkBFomM22hTLKCIS5cdbIAJ9COAmU+KAi2wVSO0wQ==}
+ '@react-native/dev-middleware@0.86.3':
+ resolution: {integrity: sha512-LiEPTqTg/63bYUnrPyHLfjTDCNhA/+CUqI1+DsA9tYyewtSbULd5awsva6SgE10I+2iMhgKXS3ymkhU/kSCrGA==}
+ engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
+
+ '@react-native/gradle-plugin@0.86.2':
+ resolution: {integrity: sha512-2F6x14NcHMpVmfTTFKfMkpV5dZedZrLiv6PE+c3vgnesV2bjleUBydr4U+NI8VkI7OwW71L0A5qQ76I9LCrfoQ==}
+ engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
+
+ '@react-native/js-polyfills@0.86.2':
+ resolution: {integrity: sha512-bIwNcGBaQ74shB5z1mRkxOpjikimuwsnOCEkZSzL67Z1FTyK1ObpENfyd2QvcvVW9Cjl+tHuw9ynpBnb2jPoJQ==}
+ engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
+
+ '@react-native/metro-babel-transformer@0.86.2':
+ resolution: {integrity: sha512-mX1wgLErdb2hDgXJr9zM9SWLe+ZteZTFTwRWGOQ53yEJwc9DVSnxdTlAtVdMeOj2ntycKh0R8jYdat2Am43cwQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
peerDependencies:
'@babel/core': '*'
- '@react-native/metro-config@0.86.0':
- resolution: {integrity: sha512-7v+xbTeEci9ZcQ/Z1OqI4RXcqN69wSMDYL5BAMvOReZ7U04+aDQ0/SQhClYPn6x2/RxM4WzMKSAuNyLKqvYVtw==}
+ '@react-native/metro-config@0.86.2':
+ resolution: {integrity: sha512-hJno256j+MS0b3JD1aD3ouTGZVacKNVBuXL2atMQQ8BZ060vl1ptnZ83y569aDW+/rgFSOcqn6ydKeSz4uUKQQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
'@react-native/normalize-colors@0.74.89':
@@ -4332,16 +4371,19 @@ packages:
'@react-native/normalize-colors@0.79.7':
resolution: {integrity: sha512-RrvewhdanEWhlyrHNWGXGZCc6MY0JGpNgRzA8y6OomDz0JmlnlIsbBHbNpPnIrt9Jh2KaV10KTscD1Ry8xU9gQ==}
- '@react-native/normalize-colors@0.86.0':
- resolution: {integrity: sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==}
+ '@react-native/normalize-colors@0.86.2':
+ resolution: {integrity: sha512-EzPFc9Y6lzYOWeso2almwXI7f8+qReHxWvT+algsOczb2UhWXIWXDoSvkdwoSfiwwmGt/ijJgKJoeHlzPkLwRg==}
+
+ '@react-native/normalize-colors@0.86.3':
+ resolution: {integrity: sha512-Cv3CDkprb67GrzuaS9BGbBJC/6G4lIw3nyKOHRKTqTTum4bn37y5+R0Z04L8mcbQN85eEohNrRwb7IOM4j6uvg==}
- '@react-native/virtualized-lists@0.86.0':
- resolution: {integrity: sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==}
+ '@react-native/virtualized-lists@0.86.2':
+ resolution: {integrity: sha512-uO0J72gh3EvE+1/GHRk18QRyBDTRHRB0AraAfojsRjbT7VMuJwKrZYaKGshavoaEud6aw00ZB9/8mTMIKjjcAw==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
peerDependencies:
'@types/react': ^19.2.0
react: '*'
- react-native: 0.86.0
+ react-native: 0.86.2
peerDependenciesMeta:
'@types/react':
optional: true
@@ -5191,10 +5233,6 @@ packages:
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
engines: {node: '>=18'}
- '@testing-library/jest-dom@6.9.1':
- resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==}
- engines: {node: '>=14', npm: '>=6', yarn: '>=1'}
-
'@testing-library/react@16.3.2':
resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==}
engines: {node: '>=18'}
@@ -5874,8 +5912,8 @@ packages:
resolution: {integrity: sha512-TQf59BsZnytt8GdJKLPfUZ54g/iaUL2OWDSFCCvMOhsHduDQxO8xC4PNeyIkVcA5KwL2phPSv0douC0fgWzmnA==}
engines: {node: '>= 20'}
- agent-cli-detector@0.1.2:
- resolution: {integrity: sha512-qdZ/9JFORtTKJNhT/IczMeEfEUbUU0K5umYeiIQHX+AjHs+Y9SXVzSgaYlpZeyNMrvuh2HpZiOTpvS57iPfBkQ==}
+ agent-cli-detector@0.1.7:
+ resolution: {integrity: sha512-d8OWDVdZMgjhLUT9ZPgSv/BdFFF9pVuscC0JdUSz3bjwE15gcp6u/o0/JooM2yyAWC49KThFhXlgTXRa9B7yng==}
engines: {node: '>=18.18'}
hasBin: true
@@ -5966,10 +6004,6 @@ packages:
aria-query@5.3.0:
resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==}
- aria-query@5.3.2:
- resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
- engines: {node: '>= 0.4'}
-
asap@2.0.6:
resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==}
@@ -6039,12 +6073,12 @@ packages:
babel-plugin-transform-flow-enums@0.0.2:
resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==}
- babel-preset-expo@57.0.2:
- resolution: {integrity: sha512-PgsWlNSlNhnSkU4UCAcvl252CaB7VU1eQuSvJoRSiqfrZDGmeqwQmMUs+PcS5VUppFwmmhVlDPwOc7kR3Qr08Q==}
+ babel-preset-expo@57.0.10:
+ resolution: {integrity: sha512-08bt6bqMZFoQuWO9gkt0EXotbqKHimELFGa1LlKJvY89iKsi4S0FJSFBFJ4pn9NZvofet48HIjYDQ0SZJ7bNrg==}
peerDependencies:
'@babel/runtime': ^7.20.0
expo: '*'
- expo-widgets: ^57.0.3
+ expo-widgets: ^57.0.16
react-refresh: '>=0.14.0 <1.0.0'
peerDependenciesMeta:
'@babel/runtime':
@@ -6578,9 +6612,6 @@ packages:
resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
engines: {node: '>= 6'}
- css.escape@1.5.1:
- resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==}
-
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
@@ -6889,9 +6920,6 @@ packages:
dom-accessibility-api@0.5.16:
resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==}
- dom-accessibility-api@0.6.3:
- resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
-
dom-serializer@2.0.0:
resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
@@ -7381,28 +7409,28 @@ packages:
peerDependencies:
expo: '*'
- expo-asset@57.0.3:
- resolution: {integrity: sha512-6E08JDuYrktY5pKNn2WiFBPBfj6bvFb++ccuBTijK8BUk/afSoy6n35h583LmC2YHgHTepWHHWNfr61nYcMd8Q==}
+ expo-asset@57.0.16:
+ resolution: {integrity: sha512-IBRfQdW3iFT+GOBERMZLZM1MUNyrjMgMskuD0elVZ1ae44858UFlTJkHO3f8vi+u5zuv7O7KofsiN8NMG/uWzw==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
- expo-audio@57.0.0:
- resolution: {integrity: sha512-4eV3rIFksVygrnm/dxdrP3Umjsn9/1Zr6cgBzoFtm43nw65g4s3WB5nkszG3JB4a2zpQ9N/IRS5EdRqXqLdbSg==}
+ expo-audio@57.0.4:
+ resolution: {integrity: sha512-TLP8rt1UvUDzgxGnyQ0TR9hV6tNP/UJQdDu7mSK+dgEydMFoptq55D3hcUoB1gF39f3/3AUuWfOtpGM+4N4X1A==}
peerDependencies:
expo: '*'
expo-asset: '*'
react: '*'
react-native: '*'
- expo-background-task@57.0.2:
- resolution: {integrity: sha512-DA5VIVmbY/f2wlrSNW+EjsagCaAmyuvy1OXwouvTPFLU4rX7lFAPRw1+kb0fXSeq8fO9Z4s1lN4x/vTParoLWw==}
+ expo-background-task@57.0.16:
+ resolution: {integrity: sha512-RWn9BrGHvEKh9YVgOWkjFEVlcam2vLeDdSagOJxOcukR1x1Xya3xD4KSBvcQDxpnfBVMIUruymLN6F0woYNarw==}
peerDependencies:
expo: '*'
- expo-camera@57.0.1:
- resolution: {integrity: sha512-OtQOOFQ8djlB/UXlETa+Agu9m88P6d1go0VIO9b/a54AFUuowUqEH9vpzkq5iJ4jVhvJSblm/II7kLtwNnxW7Q==}
+ expo-camera@57.0.4:
+ resolution: {integrity: sha512-MqbbQ63O+cA8JbK3lfFHiD0f2jv8jB+EG/ILK6f5xnOPV9IR37yaG5+lGtRZNoOeYBzGypyOi17USItleMyuGw==}
peerDependencies:
expo: '*'
react: '*'
@@ -7412,15 +7440,15 @@ packages:
react-native-web:
optional: true
- expo-clipboard@57.0.0:
- resolution: {integrity: sha512-eIt47f6kvR33HOnIEWvs9l1AUAY4GeLY27szwamSuwRl7nntNythSFFbmnrEtiSUaz//y8XVqCG9WLpdMkOpjA==}
+ expo-clipboard@57.0.1:
+ resolution: {integrity: sha512-HWICri4+1ao7S6QEfcorxVumXDiDnx1guGGewjZgGJWLGxFYs0RgH8ujBs+lkTzBkMmlwADaWSlaesR+nDJt5Q==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
- expo-constants@57.0.3:
- resolution: {integrity: sha512-BghbZlzwFnA22BG0CWv6W29zx8w19FozYPfSeZ3HjMitoy4aAmF1FnFbbjYSmZz6HHXXTWOfqwobOEIaglfzxg==}
+ expo-constants@57.0.17:
+ resolution: {integrity: sha512-cPWYBKN1SEbg2lXg2f8VkePJqGZrJPLvVdQDCTfbFu9sQHO1M31Y1zILReUuGnmt/VKeUz40ze579vR00xGu/A==}
peerDependencies:
expo: '*'
react-native: '*'
@@ -7430,13 +7458,13 @@ packages:
peerDependencies:
expo: '*'
- expo-dev-client@57.0.5:
- resolution: {integrity: sha512-OOQJHb4Cf403AhZAbcZg0OOVJKLc1HArVck92LHd9PO2HuTadj/6YAlWIyXXYfeYYdjoVgLd/cAXnaEAwp6HKg==}
+ expo-dev-client@57.0.18:
+ resolution: {integrity: sha512-cT+6iO4rRoguIxeONAPHF2wbAlc002tZya2M3xHlc6EWKptjsAL2Vr+kagYdLH/4vtLX7XKhWvOzaBf+rQpRFg==}
peerDependencies:
expo: '*'
- expo-dev-launcher@57.0.5:
- resolution: {integrity: sha512-f76DIU/mF3YdDZOgwLL3A5gwb3NVIySLE5ot+gy1T0pFo6VPW3Ew7h8oyG9fQdYum58v/3LoB1uWAIhhkHhsqA==}
+ expo-dev-launcher@57.0.19:
+ resolution: {integrity: sha512-IF/EJ3tcHqiyfBb5VtpAU3upBXRVTFVuVoWqEzrkZCesuMi35td4yQFvPtqXbMceaEErgFyiyxhW+L6AXVYapA==}
peerDependencies:
expo: '*'
react-native: '*'
@@ -7446,65 +7474,65 @@ packages:
peerDependencies:
expo: '*'
- expo-dev-menu@57.0.5:
- resolution: {integrity: sha512-ITQdZBawOe7Z9aB9erLNKIM/0X1iL6VvTrC5By6ECQqTptiRLJmpeqCt1vCJ72Wz+8YeHk+n2GY5V+1yzXf5OA==}
+ expo-dev-menu@57.0.18:
+ resolution: {integrity: sha512-H7m9ENtVYz4FpJktQj+z/njIN1t7kOEaKCv8Qgv2SdAlyFTW8s2EnufxPoSlg2zUTanaauJ6wrR6XESHwW7kpw==}
peerDependencies:
expo: '*'
react-native: '*'
- expo-device@57.0.0:
- resolution: {integrity: sha512-XiTtUhyS64xO8AtEY14FI1vJpUT5b+cyxhjAsHIsoAx6y4rLF719pVY2sNx1MJYb/l7qa9kxWAcMDfk8Q8Gcpg==}
+ expo-device@57.0.1:
+ resolution: {integrity: sha512-jyEMDUticH+dhcL3GHa2aiifOvGXJsmb3oVT2R2q4i8bN7Bddy61+NkpMmuS2VAZrvoLQwf0TJJ/1vi1ukvutA==}
peerDependencies:
expo: '*'
- expo-document-picker@57.0.0:
- resolution: {integrity: sha512-foT1pA3tD7KvcqXwjQBh4qEDjHT9U9MhZ6H2aF38VolCiHRhXDQLtKQqCeXjbHxJhSf8HC/YPV3IRxDxFTL2sA==}
+ expo-document-picker@57.0.1:
+ resolution: {integrity: sha512-qBwM5oxDZ3I9kwFD3pUE1oK/WNv9artoEKO6UpqhQgNRr0XA1ALRVWYjkF4+ge9lUNDRehjTm/jenINkzqg84g==}
peerDependencies:
expo: '*'
- expo-eas-client@57.0.0:
- resolution: {integrity: sha512-HYroDzqYFpALIXW5tpQD2WHqij3iUal+fXzVJhIYV3OdVcbobxt+p6209OJDARlU431CANZwp4vPmS7yRCWTCw==}
+ expo-eas-client@57.0.3:
+ resolution: {integrity: sha512-cyC2AZg85DfAt4Ge7jIRmxmL62dUXob4EnrLZatrl+xJ6AuObqjMYf3hx5JYFfvro4ajkFo25qNREPTdiHCYQQ==}
- expo-file-system@57.0.0:
- resolution: {integrity: sha512-G+eytNuNGMiS7dbdj1j0nAYMowXnNr1vpO+jss6nQvBlRxshcGBFMtk8xfc67ynz0MUVkzod7WwKO7Vf1iOaJw==}
+ expo-file-system@57.0.6:
+ resolution: {integrity: sha512-pm8PMYEW6BnVOCBJ7df9FcDmQtE1tqImuYphlfYe1ipQRLYtdCayRczJcbKIsnB3mqEyp4gC2gWmMbsAsAOjVg==}
peerDependencies:
expo: '*'
react-native: '*'
- expo-font@57.0.0:
- resolution: {integrity: sha512-zF+J7WrNFjqyAADwdvDgkFEoIQv9DqcjJ57HVstNEH7/7Tx1ThPEhKraodEQOwSjMYWirP+4BYsVbdb+/Zr4QQ==}
+ expo-font@57.0.3:
+ resolution: {integrity: sha512-kiVUnc2A8vAvO2FfDJTsQa5BwmY+PAkof/1wRb5MOkcX1jtiaSTwz9gCUAyscMBFLundZDmZrLy1P7LZVC+NvA==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
- expo-glass-effect@57.0.0:
- resolution: {integrity: sha512-QadzKSKpjXOlkIEnkX7SqHwCgUJZLjhWUvg1AauaUgKVnz35Ror41juM4y+KNr9pFxnukSDUz8L+UvdZVCNu6Q==}
+ expo-glass-effect@57.0.1:
+ resolution: {integrity: sha512-m/n8maxqNcHk6ZDhuqXBfD5Kt1Iz3M8xykVgdB0iSCIXvF70IqWXmQhX8Psswhrp8eZ+3r0mAD0Jh/2gFA3QaA==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
- expo-haptics@57.0.0:
- resolution: {integrity: sha512-va+uB/DoMlT9f0OJ8v24iNqs+YH6Q6wip+LLjVvalbJzGK8LvZQ+gIiauogFxAAFLQIJzrQVJGf+0BmV3q13bQ==}
+ expo-haptics@57.0.2:
+ resolution: {integrity: sha512-vPths6zTxxaGaemC7D1GKbw1iOnOAAL5oAcCFVSgVNWnuLCBefL1LkhRBaJsepiMXRh2y3vwPiXkPaf6d16blA==}
peerDependencies:
expo: '*'
- expo-image-loader@57.0.0:
- resolution: {integrity: sha512-EhwnoPC4T/EMdB7Nsg7qITzhO/qB0hUnmVghmtAKQwwDVaLWWYCGE4NLkes3zk9Ub451SmS/swgPp4PCPzOlnw==}
+ expo-image-loader@57.0.1:
+ resolution: {integrity: sha512-uhrZKLT/cTl2mXyR28kPpVkS5O+PK9N1QA/07IFM4f5T4g0lTW1JHT3NEWwEEsGFldPmVX4j7LwUVVZxE+woug==}
peerDependencies:
expo: '*'
- expo-image-picker@57.0.2:
- resolution: {integrity: sha512-XW+C5weIthkOLCJZzExeRJf9pcWfaXNHSDm76gmZVhDUVIHdi9IS3eihAIF0WA0fBo9ogIfQs/iep/c6CzIMKg==}
+ expo-image-picker@57.0.16:
+ resolution: {integrity: sha512-9CrY/hoBHI2GsOo/4AWz6cInRHhGhhCmbza3JYr4j3P/NWpthLlIm0IXSWpxadSYJx0a2FAti0f7XWMg95aPEg==}
peerDependencies:
expo: '*'
- expo-json-utils@57.0.0:
- resolution: {integrity: sha512-GJMjJlS3ZRTXWkKJPXY9OjscEEyPxbvNURBJ9Gkd3KxS4Hzof+EdK8pF+3Ty4ec435ciYm9ll+IWznuVHYMiow==}
+ expo-json-utils@57.0.1:
+ resolution: {integrity: sha512-cgTe1NqzQdYs/WN+3nIY5IZg8s0pb0xaTUbhYvxQDn137GbwRfHoGM2se3m3Vsl4Qu+B9G4RPEK5WJDEU2Do7g==}
- expo-keep-awake@57.0.0:
- resolution: {integrity: sha512-WqEoyDNSmUeAI9Gu7UaWKDjOhfaV+jGcms7N0hh/EAr7sqJrI2s0HpLSC3P9cWfXFUZCL1zZjd22m/NbsJYCKg==}
+ expo-keep-awake@57.0.1:
+ resolution: {integrity: sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==}
peerDependencies:
expo: '*'
react: '*'
@@ -7516,14 +7544,14 @@ packages:
react: '*'
react-native: '*'
- expo-linking@57.0.2:
- resolution: {integrity: sha512-A7912NxC4ibDiv7fN2BuEWCrTCmmWdaD+bQa4iNddPDOa3Svs1YDoWdbhJt3mniSaAh9AnUKS2MdFbO4zk8z+Q==}
+ expo-linking@57.0.9:
+ resolution: {integrity: sha512-TQJe3NsnpZXApBg1n6DKaZ4TNhYm+iYWq7W9rTp+G44IBMZSUcI8ba+ffYC/LMY1qw+95AuBPIF1zTx1NQoTXw==}
peerDependencies:
react: '*'
react-native: '*'
- expo-local-authentication@57.0.0:
- resolution: {integrity: sha512-/ZzS7OD5OaAEhSF/+zPWZqzT8xWy1rMICfDqP5ojJ6Flh4wgAOedG37p8UZlTQYJRR5u1NQ9AoAePV8H/ky14g==}
+ expo-local-authentication@57.0.2:
+ resolution: {integrity: sha512-8K4zcrQ5wZkRS1rwEWY8qHbeGVMNh35e9D1VTVKlMHz1O47itW+pW6iBVuqwGFLozN4fRBeEDQH8hu9Gt58YuQ==}
peerDependencies:
expo: '*'
@@ -7533,17 +7561,17 @@ packages:
expo: '*'
react: '*'
- expo-manifests@57.0.0:
- resolution: {integrity: sha512-JDKDuF1gd3wPywhu+VzH+qFNDlu91Unqpcre6R9b14MEMq5L3mG6XL9LOySChiWlLJAoObeNPdVV7Od6P++49g==}
+ expo-manifests@57.0.1:
+ resolution: {integrity: sha512-qB/mDG2dYdl+EvUeQuqP8KFYCFgFCQjJYdWIHo8SFBgDzMYmdF286DFY2M1M9Okr99wkb5M4tgA3aCcwv3aEQA==}
peerDependencies:
expo: '*'
- expo-modules-autolinking@57.0.5:
- resolution: {integrity: sha512-Vv2PVeEUN0/VW19ItkVA8LxAuH8SV8cjui0MKhJxCKrNyCAGGZU6sRsBJciYXR4uVxuXWKiepGW6E3k/c+Ytdg==}
+ expo-modules-autolinking@57.0.12:
+ resolution: {integrity: sha512-Q8KAlq37nLKsQ+HsS9NpQVpd5jCgqtu694TDUNHBUBpV9ViD82mRBh8Uug/h68RG9xnLS+kuL4nYaCuFRghHjg==}
hasBin: true
- expo-modules-core@57.0.3:
- resolution: {integrity: sha512-fuD3DjPQvdaldtCJm2erV2/trPMrDJvEwPdz0RuxAQnduiNwZ3yxBoi81ZEKC5GBSJauMo53YXSwogsFagjwFQ==}
+ expo-modules-core@57.0.16:
+ resolution: {integrity: sha512-vPbxDOdvG6Ucpws5ucdbQa+nNmdxKONS1fTg92sy0p4TnHX8hBiExtbrtUA/5+1t3FRCUuiy29/1nJEGdt3E4A==}
peerDependencies:
react: '*'
react-native: '*'
@@ -7552,8 +7580,8 @@ packages:
react-native-worklets:
optional: true
- expo-modules-jsi@57.0.1:
- resolution: {integrity: sha512-dECN3pOFv+KrQcGrOhJKEBc1/ob7SBjTTYGRB1bc84zmQ65La0FSrAPxpvnEQf8g9giLB5y9RLqwGxHspjXigQ==}
+ expo-modules-jsi@57.0.8:
+ resolution: {integrity: sha512-MFwvKP0FXLkEET27KsKvOh/L7Dpu2d00UgzAlmKozJgtI23WTKSyKPxu+EOSs9peSgkHzKI0vQOhWjOseGBMkQ==}
peerDependencies:
react-native: '*'
@@ -7563,29 +7591,29 @@ packages:
expo: '*'
react: '*'
- expo-notifications@57.0.3:
- resolution: {integrity: sha512-BTfrlnWF4EQZ0Oufsj00OesdxxlXCaC3OexnJTWKdQJ2a26w/bIAM3kXIxHsizmBWaHt5EJc5LvuDzVeUt7JJA==}
+ expo-notifications@57.0.17:
+ resolution: {integrity: sha512-P/9ZqBOlFBVCZK3T5bhqORtWJhtVBcXQXe/MY1YWuKaxWgKvoGeEvD9ItWuo87jg2nD+eloVmFYqwdhhVCxPlA==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
- expo-router@57.0.4:
- resolution: {integrity: sha512-JVUAxamOQV7oG/uo/itLuR8gLiN3+C00bg/JPU+p/g/8Mx3PceZ4Mga/34cLmMc+RETpi5EjC/+AN0mJJzOW3w==}
+ expo-router@57.0.19:
+ resolution: {integrity: sha512-JovzZ066+qhykyJLC4hSEJpM8ToVWnJqbxmTN7USzARM0HgRR/g05a75f/EIsY4S//hbPGm6wGl/ObcCVtAX+A==}
peerDependencies:
- '@expo/log-box': ^57.0.0
- '@expo/metro-runtime': ^57.0.3
+ '@expo/log-box': ^57.0.4
+ '@expo/metro-runtime': ^57.0.15
'@testing-library/react-native': '>= 13.2.0'
expo: '*'
- expo-constants: ^57.0.3
- expo-linking: ^57.0.2
+ expo-constants: ^57.0.17
+ expo-linking: ^57.0.9
react: '*'
react-dom: '*'
react-native: '*'
react-native-gesture-handler: '*'
react-native-reanimated: '*'
react-native-safe-area-context: '>= 5.4.0'
- react-native-screens: ^4.25.2
+ react-native-screens: ^4.26.0
react-native-web: '*'
react-server-dom-webpack: ~19.0.4 || ~19.1.5 || ~19.2.4
peerDependenciesMeta:
@@ -7602,31 +7630,31 @@ packages:
react-server-dom-webpack:
optional: true
- expo-secure-store@57.0.0:
- resolution: {integrity: sha512-vkP16rhW7b4bljW5BC4kKXBpNxQ0O1E9SpI5NIfh2biZnszLTpI/gUF4oBsvOY2nvkh7oXS2ERuUoA8cuS8FWQ==}
+ expo-secure-store@57.0.3:
+ resolution: {integrity: sha512-w7XkSQeUiYXPoKXo1jSrQqql7pyCSyIzOp2k0apsZZBE+RkoLTQIMjcbqc181y6KgupfNnYxkaKe+4MEg94+SA==}
peerDependencies:
expo: '*'
- expo-server@57.0.0:
- resolution: {integrity: sha512-18byjwFbHVFrGzI4IH1o2MZiiYwvO/y3d+JL3sq50Lg3Id1titwIRMh1fjbHbpM+wP6x14KJY0Ers1UYsjGq8Q==}
+ expo-server@57.0.3:
+ resolution: {integrity: sha512-aK+LdKzauHSGmsOStZtyxdzv0zWssCkxTw3m4QuOhfDSJsZaMRTd9O41d8ixU/QfELTbaJ0oRNcF7JFV/7O9YQ==}
engines: {node: '>=20.16.0'}
- expo-sharing@57.0.3:
- resolution: {integrity: sha512-0bCVd27Hb0eokJ0UfafpfGwzePXjaxfgayQQSPXN2tsWDTYKtU2soIMF2+KkgEh7MBLq4PwdQmntPVcapAMnKA==}
+ expo-sharing@57.0.18:
+ resolution: {integrity: sha512-Q09Gab1yn+OLR7sqcHBNyjM/4MMQa290hDKCV2pf2THnTLkLLuUmC0AKgMgxygqU0uuzhOgVqfnE70dePCLq9A==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
- expo-sqlite@57.0.0:
- resolution: {integrity: sha512-BYTaLjO9+CMaEYIp5fdrO0RGTkaumZrNuDSQgFkeZe1MfKeDjiS1myg66jxJviWaqpycLq7QnRT11+bdO23JHQ==}
+ expo-sqlite@57.0.2:
+ resolution: {integrity: sha512-5KVbT7BQFZlIcQBXKWvwBERK3ODF6PSqct27GQKukZzAjsx2B97R+mwtYqrTUtUbw71VJrQrdr9Yl2OWYC7UpA==}
peerDependencies:
expo: '*'
react: '*'
react-native: '*'
- expo-status-bar@57.0.0:
- resolution: {integrity: sha512-wq1fDVAjfrzCj67hOcvEkGpgRrb6xVI7oSA3bfsQ08SFk8il7vGixJq+xyEbtdcrI9CDiNKQrT3ZIjgnyGbbBA==}
+ expo-status-bar@57.0.1:
+ resolution: {integrity: sha512-Xwaq1gAoVRWx5dPG5VhT5RSbnI9OilhZnO5qoPBnUaBAa5VzRzfdS8q0/bsPt0jR2DKLtGuP0bQ6efMJ4RIMDg==}
peerDependencies:
expo: '*'
react: '*'
@@ -7635,27 +7663,27 @@ packages:
expo-structured-headers@57.0.0:
resolution: {integrity: sha512-//t9UNPbJSEysc2x4VKJG/u7Osvv5DYJWsET5bqt/B+qcD1by/JXvSQzX3Q/YAgA96xFPontrz6OAPLbO4JKEA==}
- expo-symbols@57.0.0:
- resolution: {integrity: sha512-pLjyiSqOV8NEqs2LqVZmF0cS1j90XoeT2oHbFciVjW7DiV7cP34SGLUgeDsOWY54QkWFR5p2Gz9LZWM8eBsSkA==}
+ expo-symbols@57.0.2:
+ resolution: {integrity: sha512-qZ0iqOflm5lZGwRsQ5Y8sDksw3GAUKwHSX1bJBoocXf7gu14vafIXXYWte+JT9VfXAUGyKodJhLHP/GoOrcNWg==}
peerDependencies:
expo: '*'
expo-font: '*'
react: '*'
react-native: '*'
- expo-task-manager@57.0.2:
- resolution: {integrity: sha512-UMhs+Vc8TQoJOJRRRNapKNEFcBjmUIP0kFpk9xHBRZc//I9qkfxTG6Pxgakg7j7Be/6v03HO68wlIL8KwbPzHA==}
+ expo-task-manager@57.0.16:
+ resolution: {integrity: sha512-/GaGM1t+rA4PXtoMavL4HJwl9cUjbh3/u7LHGT2R0yzBiBGJ/nU0EQMx52qngrmZXebOwg4v39xcdBDC6190dQ==}
peerDependencies:
expo: '*'
react-native: '*'
- expo-updates-interface@57.0.0:
- resolution: {integrity: sha512-AQASxPDpUjHG55R4WXZ5mLu0rE4F2T/JfHOsXLfZPS1A268ynHHFv+MnZ99/Gb0xWvg3ZjNTgXPEWoRCShSJJg==}
+ expo-updates-interface@57.0.1:
+ resolution: {integrity: sha512-+LUWwJ0gf/TEKMVdQAw/Gjih4dvrk+URgy24X9qEGKuuMDZqjBRm9T4yQyBVALGL5TTdPUaB6ILxx3lshm3pwQ==}
peerDependencies:
expo: '*'
- expo-updates@57.0.6:
- resolution: {integrity: sha512-w2bROszU/n4ZwHjUC+sRBwVWCkn1yoe5KrEOMS+q7r0OEj/8a+jUNIZbG6fJAQqjW74x8/gqulaxJRwZgGGlQg==}
+ expo-updates@57.0.21:
+ resolution: {integrity: sha512-JwTmguNvBvISEz9ALwrT8yDLnYuSP4/6kUz0kXkQuC8izebtTGkRPR37Cv39KPheDDA1F+QPhNyvw4lXM/BPvA==}
hasBin: true
peerDependencies:
expo: '*'
@@ -7666,14 +7694,14 @@ packages:
expo-dev-client:
optional: true
- expo-web-browser@57.0.0:
- resolution: {integrity: sha512-J+kQAqbH423Ge6YXrQTVKkmH+cS2SSjnc7ocoRDjhmfZ2+XSBXX3Ogds5/aEKjNKUe7ObtPMkVq9uWbXJv4BGw==}
+ expo-web-browser@57.0.2:
+ resolution: {integrity: sha512-3vl5kvd7PB48ub6PpNIJUuPxO8xVa6D8RnIgNba6SXRwqFprOfeEZgwTgtm41kz0AAtvMOztUVNEUkwrHKjqMQ==}
peerDependencies:
expo: '*'
react-native: '*'
- expo@57.0.4:
- resolution: {integrity: sha512-5wW0SR6OnGUh+UPb3JWlFTAL22IgBcX36RqofjLAH2AuFqxAIpYIdwr9BYR9Wl3xyIe0t9lCoMQIZlAIZUNQvg==}
+ expo@57.0.20:
+ resolution: {integrity: sha512-H57GWLLpNPrltsrjvx6ROsMecoFQza7h9OzujUjBsaY+aDL+bj3GsZaH3T5v5B96R7txuidUwjUh5q5hi3qttg==}
hasBin: true
peerDependencies:
'@expo/dom-webview': '*'
@@ -8095,8 +8123,8 @@ packages:
hastscript@9.0.1:
resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==}
- hermes-compiler@250829098.0.14:
- resolution: {integrity: sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==}
+ hermes-compiler@250829098.0.16:
+ resolution: {integrity: sha512-xsgzk+mUyvt9t1nUbF8USBlYxajTUtPJhVZ86q85s/SEoMKCF+52YZcudb0ENSnV3T3lV9mgB3s6R7+pH90zgw==}
hermes-estree@0.25.1:
resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==}
@@ -8230,11 +8258,6 @@ packages:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
- image-size@1.2.1:
- resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==}
- engines: {node: '>=16.x'}
- hasBin: true
-
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
@@ -8250,10 +8273,6 @@ packages:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
- indent-string@4.0.0:
- resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
- engines: {node: '>=8'}
-
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.
@@ -8939,61 +8958,61 @@ packages:
mermaid@11.16.0:
resolution: {integrity: sha512-Zvm3kbstgdpvIJPPItlL7fppIZ3kibvc1oZIGxdvk9t6UFz6flv+Jw7FtRGKwfcI8OckmH04LqG6LlS6X4B1pA==}
- metro-babel-transformer@0.84.4:
- resolution: {integrity: sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==}
+ metro-babel-transformer@0.84.5:
+ resolution: {integrity: sha512-2WbHILKMiJUzfdjmGOQOqU1bWi9//gqiclc/tkk/AIsrrVw3efhZ1uhkOwMTxUEPOzqoo091H0olLmVZH5FHGQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-cache-key@0.84.4:
- resolution: {integrity: sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==}
+ metro-cache-key@0.84.5:
+ resolution: {integrity: sha512-3dPB2TnvGjjf0/9O7AXVQURKXuQNauTZE7WpTGTlR017Gh/B5y0m/2wcqxfveUguHSpu89KhVxCAlr2k/H7uhQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-cache@0.84.4:
- resolution: {integrity: sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==}
+ metro-cache@0.84.5:
+ resolution: {integrity: sha512-WHS0n2OxQqtwEjSeQFPePNrMvEFhmQcUQM9cRJMHByWoi/GMWFBEWOf7hVkAM/0KRutAXNbDlSu/cZB6CyxgQQ==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-config@0.84.4:
- resolution: {integrity: sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==}
+ metro-config@0.84.5:
+ resolution: {integrity: sha512-zie+uN6oohscowi2S7ByU+wUw6CrT4ZxW9uAbONOObSxx86RGmnIAmjXHLkfmcdYoY7jzOPEbqcI6oeVmqyBQA==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-core@0.84.4:
- resolution: {integrity: sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==}
+ metro-core@0.84.5:
+ resolution: {integrity: sha512-xwm605hCi5Y6eJTTb8ZWo6pkUcoBEIyiQOfkZh5GwtDwUrP9SNhTQZhzJHrBCwwxlf3Ptl/pxWJgQ1rsNYMnrA==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-file-map@0.84.4:
- resolution: {integrity: sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==}
+ metro-file-map@0.84.5:
+ resolution: {integrity: sha512-mlm/JL8toSbSc2akpKIGmzvrVRSCgZ5vkbycI34oMLoOnLGuLyC8WTyVJ6P0hZG/usDaGwZSl/s9BCRriqjGJA==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-minify-terser@0.84.4:
- resolution: {integrity: sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==}
+ metro-minify-terser@0.84.5:
+ resolution: {integrity: sha512-BJoFwCEDsYnagPqarayInv2+diCDNDdLlaof/p6s9w4gh+gc9HXYM+pDvsKGKKUumpZswNF3Z/ftTMqKl/5IBg==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-resolver@0.84.4:
- resolution: {integrity: sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==}
+ metro-resolver@0.84.5:
+ resolution: {integrity: sha512-VSSnepg1k6LyCwtb6eirWdAWlpKwBG8Rdtsr1mU38rMelFyWgh3/QuMSiZIZAIjwg/fsa8GhW5/FO54CAUPCEA==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-runtime@0.84.4:
- resolution: {integrity: sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==}
+ metro-runtime@0.84.5:
+ resolution: {integrity: sha512-U1m2+d1Pr+JO2/iVXBB2OfXXityz7tqwIorxfrT15IEgaHvpJBq/OHiqnOWPKJbUl3JcxjcdviZZOKk85oK4Qg==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-source-map@0.84.4:
- resolution: {integrity: sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==}
+ metro-source-map@0.84.5:
+ resolution: {integrity: sha512-2BtV5L9uPc49F13Gn5wiP6bX/EncqzqTIk2VL/0F/96Vo0YEOjluT/qktQjFODfqGFsucwnh5mPEAl/2jVEfeg==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-symbolicate@0.84.4:
- resolution: {integrity: sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==}
+ metro-symbolicate@0.84.5:
+ resolution: {integrity: sha512-rQ40zYDAkaWBN9yvjUuAD0ZpzBMZSoKyGYXnb5JrfbKjun7fTvfoLHL3KXFYenBTYZkQtlp4cKSCv/1utxFyOw==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
hasBin: true
- metro-transform-plugins@0.84.4:
- resolution: {integrity: sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==}
+ metro-transform-plugins@0.84.5:
+ resolution: {integrity: sha512-+InaSVGaOyt0DyRo4Y/zIdPI6CZwnbNho5LAL23tgmuGwv7fyfkF7kKfPjZcfxXBcoYdTLLFnCfCH/dHSiCqNg==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro-transform-worker@0.84.4:
- resolution: {integrity: sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==}
+ metro-transform-worker@0.84.5:
+ resolution: {integrity: sha512-ui1Z8x4s5RL36gMmKLaMMO7O9NNDHNdthEZSCDQHAau3JcAsTaFOK6I+2q4I/kW5u8hSEjJk9L45TXSVJw6g1A==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
- metro@0.84.4:
- resolution: {integrity: sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==}
+ metro@0.84.5:
+ resolution: {integrity: sha512-r1liLkyFZMVSEMNjU1CJU5pRzs3NdkxHqXS60O25c0rCIqAR+cGk7rPydw/g0WAIKVXojIBIF45yYBPagJGcgw==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
hasBin: true
@@ -9157,10 +9176,6 @@ packages:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
- min-indent@1.0.1:
- resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
- engines: {node: '>=4'}
-
minimatch@10.2.5:
resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
engines: {node: 18 || 20 || >=22}
@@ -9269,8 +9284,8 @@ packages:
multipasta@0.2.8:
resolution: {integrity: sha512-ZPWuMKyv0cSO29f7hozp+k6+crZbQijV8ipMvxNxRf2SwtYGTX1ZX89Kd20VV4H9Znonx+EQn+iy1wGQsJ+b+Q==}
- multitars@1.0.0:
- resolution: {integrity: sha512-H/J4fMLedtudftaYMOg7ajzLYgT3/rwbWVJbqr/iUgB8DQztn38ys5HOqI1CzSxx8QhXXwOOnnBvd4v3jG5+Mg==}
+ multitars@1.0.2:
+ resolution: {integrity: sha512-6GwVw5eLi9sThdtlS4PKwC7yRLaf45pYhIEzKBHdKxi+YOXGKFX8acIniH+Uh/+k9mS2lQOupTccjoe5r0/1IQ==}
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@@ -9387,8 +9402,8 @@ packages:
nullthrows@1.1.1:
resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==}
- ob1@0.84.4:
- resolution: {integrity: sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==}
+ ob1@0.84.5:
+ resolution: {integrity: sha512-aH9RkoZc7w/90HBamFxTw8ZLFr05wXS+iOnvmrgo53Ep8Pyrm5FieQSaPIVROkfFVQISeD/zo92fes26TOwe+A==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
object-assign@4.1.1:
@@ -9839,9 +9854,6 @@ packages:
queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
- queue@6.0.2:
- resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==}
-
quick-format-unescaped@4.0.4:
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
@@ -9938,8 +9950,8 @@ packages:
react-native: '*'
react-native-reanimated: '>=3.0.0'
- react-native-reanimated@4.5.0:
- resolution: {integrity: sha512-+iPfvK34PKKYP/p/4TaBliFkbfvjGDIvXuiiaxvISP5ip7sWegvlacwU/uAV6zNDSSmX0tDyER7PurPMKGDipA==}
+ react-native-reanimated@4.5.1:
+ resolution: {integrity: sha512-RnMvtDuR+68ig864gAvZCOdZehqhC5rFmMo0kn+ARfgVSTvFeF6IFLBVgMPUu0KwihaapEyW24WRi6nEyy1kSA==}
peerDependencies:
react: '*'
react-native: 0.83 - 0.86
@@ -9951,11 +9963,11 @@ packages:
react: '*'
react-native: '*'
- react-native-screens@4.25.2:
- resolution: {integrity: sha512-1Nj1fusFd+rIMKU/qC9yGKVG+3ofh11d3OdBQKL1iVvQfKvcB8vhvTGQf2TkfxW3bamxN+hCZIXmNuU0mRkyDg==}
+ react-native-screens@4.26.2:
+ resolution: {integrity: sha512-2XnWsZToKj76trGtEZzx5ELD/qOICFEprEeUntImmitQFVUkea27fiWdUSITArI356Y1qynpXZINW+Unbhky/A==}
peerDependencies:
react: '*'
- react-native: '>=0.82.0'
+ react-native: '*'
react-native-svg@15.15.4:
resolution: {integrity: sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A==}
@@ -9975,20 +9987,20 @@ packages:
react: '*'
react-native: '*'
- react-native-worklets@0.10.0:
- resolution: {integrity: sha512-JhE6IxDf6iabC0qu3+TAKA4v9RlluXmoIngPQX7/QUByf75lfrsHZ6/dQhyjEWnp1EEQiwzz8Cpew140ZcewDw==}
+ react-native-worklets@0.10.1:
+ resolution: {integrity: sha512-62mRM19bDpfpdI8HLkEErcdOsrAPDtE9lA/sw+5lLRpzBHNhxaoj9QyY2KjXqUmirelxkX4zuPGTC3VdA0feJA==}
peerDependencies:
'@babel/core': '*'
'@react-native/metro-config': '*'
react: '*'
react-native: 0.83 - 0.86
- react-native@0.86.0:
- resolution: {integrity: sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w==}
+ react-native@0.86.2:
+ resolution: {integrity: sha512-zbJXGZpwfZGA79Z9ob6Atvfx4nAQL8yJBa35s58E4Oo+khPykfQP2sTeumkKbjwajFYfVayg8pj7Il9nIfTk7A==}
engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0}
hasBin: true
peerDependencies:
- '@react-native/jest-preset': 0.86.0
+ '@react-native/jest-preset': 0.86.2
'@types/react': ^19.1.1
react: ^19.2.3
peerDependenciesMeta:
@@ -10071,10 +10083,6 @@ packages:
real-require@1.0.0:
resolution: {integrity: sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g==}
- redent@3.0.0:
- resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==}
- engines: {node: '>=8'}
-
redis-errors@1.2.0:
resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
engines: {node: '>=4'}
@@ -10286,6 +10294,11 @@ packages:
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
+ sandbox-cli-detector@0.2.0:
+ resolution: {integrity: sha512-4lyHX0ZU0AZKwjgZ1InxZAa3PNpyEb8rOQ+Zss1ReYmhNzW0Q+h1zE5nvniXN0HaAWZaZE1zgVNEirb0R7LmNg==}
+ engines: {node: '>=18.18'}
+ hasBin: true
+
sanitize-filename@1.6.4:
resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==}
@@ -10599,10 +10612,6 @@ packages:
resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==}
engines: {node: '>=12'}
- strip-indent@3.0.0:
- resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
- engines: {node: '>=8'}
-
strip-json-comments@2.0.1:
resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==}
engines: {node: '>=0.10.0'}
@@ -10987,8 +10996,8 @@ packages:
unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
- unimodules-app-loader@57.0.0:
- resolution: {integrity: sha512-1hHiDvLMu4dwTnFzEjFKa/HS6TvTrO4qdbFWyPENCElab5BqeATMPLP1NtNPE4p2pwiJveVHuq6qmjDvdfj7VA==}
+ unimodules-app-loader@57.0.1:
+ resolution: {integrity: sha512-wey5ChoJkCTq0j0JWdIMu2QB81vVrdhmrNAP14ZZ6WDslnZ7ff7Ezv8rMdEnVHaCKz3xK4mIVXbVU51xHgdyCA==}
unist-util-is@6.0.1:
resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==}
@@ -11505,8 +11514,6 @@ packages:
snapshots:
- '@adobe/css-tools@4.5.0': {}
-
'@antfu/install-pkg@1.1.0':
dependencies:
package-manager-detector: 1.6.0
@@ -12342,12 +12349,12 @@ snapshots:
'@babel/helper-string-parser': 7.29.7
'@babel/helper-validator-identifier': 7.29.7
- '@bacons/apple-targets@4.0.7(expo@57.0.4)(supports-color@8.1.1)':
+ '@bacons/apple-targets@4.0.7(expo@57.0.20)(supports-color@8.1.1)':
dependencies:
'@bacons/xcode': 1.0.0-alpha.32(supports-color@8.1.1)
'@react-native/normalize-colors': 0.79.7
debug: 4.4.3(supports-color@8.1.1)
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
glob: 10.5.0
transitivePeerDependencies:
- supports-color
@@ -12425,7 +12432,7 @@ snapshots:
optionalDependencies:
electron: 43.2.0(supports-color@8.1.1)
- '@better-auth/expo@1.7.0-rc.2(5705381685e3aee0fa9e57d39f706f20)':
+ '@better-auth/expo@1.7.0-rc.2(3e673fc1d89bb5bb2e0be4405d007f02)':
dependencies:
'@better-auth/core': 1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.7)(kysely@0.29.4)(nanostores@1.4.2)
'@better-fetch/fetch': 1.3.1
@@ -12433,10 +12440,10 @@ snapshots:
better-call: 1.3.7(zod@4.4.3)
zod: 4.4.3
optionalDependencies:
- expo-constants: 57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
- expo-linking: 57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
- expo-network: 57.0.1(expo@57.0.4)(react@19.2.3)
- expo-web-browser: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
+ expo-linking: 57.0.9(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ expo-network: 57.0.1(expo@57.0.20)(react@19.2.3)
+ expo-web-browser: 57.0.2(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
'@better-auth/kysely-adapter@1.7.0-rc.2(@better-auth/core@1.7.0-rc.2(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.1)(@opentelemetry/api@1.9.1)(better-call@1.3.7(zod@4.4.3))(jose@6.2.7)(kysely@0.29.4)(nanostores@1.4.2))(@better-auth/utils@0.4.2)(kysely@0.29.4)':
dependencies:
@@ -13101,33 +13108,33 @@ snapshots:
'@expo-google-fonts/material-symbols@0.4.38': {}
- '@expo/cli@57.0.6(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(@typescript/typescript6@6.0.2)(expo-constants@57.0.3)(expo-font@57.0.0)(expo-router@57.0.4)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)':
+ '@expo/cli@57.0.22(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.15)(@typescript/typescript6@6.0.2)(expo-constants@57.0.17)(expo-font@57.0.3)(expo-router@57.0.19)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)':
dependencies:
'@expo/code-signing-certificates': 0.0.6
- '@expo/config': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/config-plugins': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
'@expo/devcert': 1.2.1(supports-color@8.1.1)
- '@expo/env': 2.4.1(supports-color@8.1.1)
- '@expo/image-utils': 0.11.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/inline-modules': 0.1.2(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/json-file': 11.0.0
- '@expo/log-box': 57.0.0(@expo/dom-webview@57.0.0)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- '@expo/metro': 56.0.0(supports-color@8.1.1)
- '@expo/metro-config': 57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(supports-color@8.1.1)
- '@expo/metro-file-map': 57.0.0(supports-color@8.1.1)
- '@expo/osascript': 2.7.0
- '@expo/package-manager': 1.13.0
- '@expo/plist': 0.8.0
- '@expo/prebuild-config': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/require-utils': 57.0.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/router-server': 57.0.2(@expo/metro-runtime@57.0.3)(expo-constants@57.0.3)(expo-font@57.0.0)(expo-router@57.0.4)(expo-server@57.0.0)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(supports-color@8.1.1)
- '@expo/schema-utils': 57.0.1
+ '@expo/env': 2.4.3(supports-color@8.1.1)
+ '@expo/image-utils': 0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/inline-modules': 0.1.7(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/json-file': 11.0.1
+ '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/metro': 56.0.2(supports-color@8.1.1)
+ '@expo/metro-config': 57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.20)(supports-color@8.1.1)
+ '@expo/metro-file-map': 57.0.2(supports-color@8.1.1)
+ '@expo/osascript': 2.7.1
+ '@expo/package-manager': 1.13.1
+ '@expo/plist': 0.8.1
+ '@expo/prebuild-config': 57.0.15(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/router-server': 57.0.9(@expo/metro-runtime@57.0.15)(expo-constants@57.0.17)(expo-font@57.0.3)(expo-router@57.0.19)(expo-server@57.0.3)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(supports-color@8.1.1)
+ '@expo/schema-utils': 57.0.2
'@expo/spawn-async': 1.8.0
'@expo/ws-tunnel': 2.0.0(ws@8.21.1)
'@expo/xcpretty': 4.4.4
- '@react-native/dev-middleware': 0.86.0(supports-color@8.1.1)
+ '@react-native/dev-middleware': 0.86.3(supports-color@8.1.1)
accepts: 1.3.8
- agent-cli-detector: 0.1.2
+ agent-cli-detector: 0.1.7
arg: 5.0.2
bplist-creator: 0.1.0
bplist-parser: 0.3.2
@@ -13137,13 +13144,13 @@ snapshots:
connect: 3.7.0(supports-color@8.1.1)
debug: 4.4.3(supports-color@8.1.1)
dnssd-advertise: 1.1.6
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-server: 57.0.0
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-server: 57.0.3
fetch-nodeshim: 0.4.10
getenv: 2.0.0
glob: 13.0.6
lan-network: 0.2.1
- multitars: 1.0.0
+ multitars: 1.0.2
node-forge: 1.4.0
npm-package-arg: 11.0.3
ora: 3.4.0
@@ -13152,6 +13159,7 @@ snapshots:
progress: 2.0.3
prompts: 2.4.2
resolve-from: 5.0.0
+ sandbox-cli-detector: 0.2.0
semver: 7.8.5
send: 0.19.2(supports-color@8.1.1)
slugify: 1.6.9
@@ -13163,8 +13171,8 @@ snapshots:
ws: 8.21.1
zod: 3.25.76
optionalDependencies:
- expo-router: 57.0.4(3bc51b20989ed2c3020a920bb0eeb449)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ expo-router: 57.0.19(c021d0cd4acfce308650ff4c6746808d)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
transitivePeerDependencies:
- '@expo/dom-webview'
- '@expo/metro-runtime'
@@ -13182,12 +13190,12 @@ snapshots:
dependencies:
node-forge: 1.4.0
- '@expo/config-plugins@57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@expo/config-plugins@57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
- '@expo/config-types': 57.0.1
- '@expo/json-file': 11.0.0
- '@expo/plist': 0.8.0
- '@expo/require-utils': 57.0.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-types': 57.0.2
+ '@expo/json-file': 11.0.1
+ '@expo/plist': 0.8.1
+ '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
'@expo/sdk-runtime-versions': 1.0.0
chalk: 4.1.2
debug: 4.4.3(supports-color@8.1.1)
@@ -13201,14 +13209,14 @@ snapshots:
- supports-color
- typescript
- '@expo/config-types@57.0.1': {}
+ '@expo/config-types@57.0.2': {}
- '@expo/config@57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@expo/config@57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
- '@expo/config-plugins': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/config-types': 57.0.1
- '@expo/json-file': 11.0.0
- '@expo/require-utils': 57.0.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-types': 57.0.2
+ '@expo/json-file': 11.0.1
+ '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
deepmerge: 4.3.1
getenv: 2.0.0
glob: 13.0.6
@@ -13226,20 +13234,20 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@expo/devtools@57.0.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@expo/devtools@57.0.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
chalk: 4.1.2
optionalDependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- '@expo/dom-webview@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@expo/dom-webview@57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- '@expo/env@2.4.1(supports-color@8.1.1)':
+ '@expo/env@2.4.3(supports-color@8.1.1)':
dependencies:
chalk: 4.1.2
debug: 4.4.3(supports-color@8.1.1)
@@ -13247,11 +13255,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@expo/expo-modules-macros-plugin@0.3.0': {}
+ '@expo/expo-modules-macros-plugin@0.6.1': {}
- '@expo/fingerprint@0.20.3(supports-color@8.1.1)':
+ '@expo/fingerprint@0.20.12(supports-color@8.1.1)':
dependencies:
- '@expo/env': 2.4.1(supports-color@8.1.1)
+ '@expo/env': 2.4.3(supports-color@8.1.1)
'@expo/spawn-async': 1.8.0
arg: 5.0.2
chalk: 4.1.2
@@ -13265,9 +13273,9 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@expo/image-utils@0.11.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@expo/image-utils@0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
- '@expo/require-utils': 57.0.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
'@expo/spawn-async': 1.8.0
chalk: 4.1.2
getenv: 2.0.0
@@ -13278,45 +13286,45 @@ snapshots:
- supports-color
- typescript
- '@expo/inline-modules@0.1.2(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@expo/inline-modules@0.1.7(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
- '@expo/config-plugins': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- typescript
- '@expo/json-file@11.0.0':
+ '@expo/json-file@11.0.1':
dependencies:
'@babel/code-frame': 7.29.7
json5: 2.2.3
- '@expo/local-build-cache-provider@57.0.2(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@expo/local-build-cache-provider@57.0.8(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
- '@expo/config': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
chalk: 4.1.2
transitivePeerDependencies:
- supports-color
- typescript
- '@expo/log-box@57.0.0(@expo/dom-webview@57.0.0)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@expo/log-box@57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
- '@expo/dom-webview': 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/dom-webview': 57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
anser: 1.4.10
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
stacktrace-parser: 0.1.11
- '@expo/metro-config@57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(supports-color@8.1.1)':
+ '@expo/metro-config@57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.20)(supports-color@8.1.1)':
dependencies:
'@babel/code-frame': 7.29.7
'@babel/core': 7.29.7(supports-color@8.1.1)
'@babel/generator': 7.29.7
- '@expo/config': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/env': 2.4.1(supports-color@8.1.1)
- '@expo/json-file': 11.0.0
- '@expo/metro': 56.0.0(supports-color@8.1.1)
- '@expo/require-utils': 57.0.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/env': 2.4.3(supports-color@8.1.1)
+ '@expo/json-file': 11.0.1
+ '@expo/metro': 56.0.2(supports-color@8.1.1)
+ '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
'@expo/spawn-async': 1.8.0
'@jridgewell/gen-mapping': 0.3.13
'@jridgewell/remapping': 2.3.5
@@ -13333,14 +13341,14 @@ snapshots:
postcss: 8.5.24
resolve-from: 5.0.0
optionalDependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
transitivePeerDependencies:
- bufferutil
- supports-color
- typescript
- utf-8-validate
- '@expo/metro-file-map@57.0.0(supports-color@8.1.1)':
+ '@expo/metro-file-map@57.0.2(supports-color@8.1.1)':
dependencies:
debug: 4.4.3(supports-color@8.1.1)
fb-watchman: 2.0.2
@@ -13351,47 +13359,47 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@expo/metro-runtime@57.0.3(@expo/log-box@57.0.0)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@expo/metro-runtime@57.0.15(@expo/log-box@57.0.4)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
- '@expo/log-box': 57.0.0(@expo/dom-webview@57.0.0)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
anser: 1.4.10
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
pretty-format: 29.7.0
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
stacktrace-parser: 0.1.11
whatwg-fetch: 3.6.20
optionalDependencies:
react-dom: 19.2.3(react@19.2.3)
- '@expo/metro@56.0.0(supports-color@8.1.1)':
- dependencies:
- metro: 0.84.4(supports-color@8.1.1)
- metro-babel-transformer: 0.84.4(supports-color@8.1.1)
- metro-cache: 0.84.4(supports-color@8.1.1)
- metro-cache-key: 0.84.4
- metro-config: 0.84.4(supports-color@8.1.1)
- metro-core: 0.84.4
- metro-file-map: 0.84.4(supports-color@8.1.1)
- metro-minify-terser: 0.84.4
- metro-resolver: 0.84.4
- metro-runtime: 0.84.4
- metro-source-map: 0.84.4(supports-color@8.1.1)
- metro-symbolicate: 0.84.4(supports-color@8.1.1)
- metro-transform-plugins: 0.84.4(supports-color@8.1.1)
- metro-transform-worker: 0.84.4(supports-color@8.1.1)
+ '@expo/metro@56.0.2(supports-color@8.1.1)':
+ dependencies:
+ metro: 0.84.5(supports-color@8.1.1)
+ metro-babel-transformer: 0.84.5(supports-color@8.1.1)
+ metro-cache: 0.84.5(supports-color@8.1.1)
+ metro-cache-key: 0.84.5
+ metro-config: 0.84.5(supports-color@8.1.1)
+ metro-core: 0.84.5
+ metro-file-map: 0.84.5(supports-color@8.1.1)
+ metro-minify-terser: 0.84.5
+ metro-resolver: 0.84.5
+ metro-runtime: 0.84.5
+ metro-source-map: 0.84.5(supports-color@8.1.1)
+ metro-symbolicate: 0.84.5(supports-color@8.1.1)
+ metro-transform-plugins: 0.84.5(supports-color@8.1.1)
+ metro-transform-worker: 0.84.5(supports-color@8.1.1)
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
- '@expo/osascript@2.7.0':
+ '@expo/osascript@2.7.1':
dependencies:
'@expo/spawn-async': 1.8.0
- '@expo/package-manager@1.13.0':
+ '@expo/package-manager@1.13.1':
dependencies:
- '@expo/json-file': 11.0.0
+ '@expo/json-file': 11.0.1
'@expo/spawn-async': 1.8.0
chalk: 4.1.2
npm-package-arg: 11.0.3
@@ -13404,29 +13412,29 @@ snapshots:
base64-js: 1.5.1
xmlbuilder: 14.0.0
- '@expo/plist@0.8.0':
+ '@expo/plist@0.8.1':
dependencies:
'@xmldom/xmldom': 0.8.13
base64-js: 1.5.1
xmlbuilder: 15.1.1
- '@expo/prebuild-config@57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@expo/prebuild-config@57.0.15(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
- '@expo/config': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/config-plugins': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/config-types': 57.0.1
- '@expo/image-utils': 0.11.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/json-file': 11.0.0
- '@react-native/normalize-colors': 0.86.0
+ '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-types': 57.0.2
+ '@expo/image-utils': 0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/json-file': 11.0.1
+ '@react-native/normalize-colors': 0.86.3
debug: 4.4.3(supports-color@8.1.1)
- expo-modules-autolinking: 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ expo-modules-autolinking: 57.0.12(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
resolve-from: 5.0.0
semver: 7.8.5
transitivePeerDependencies:
- supports-color
- typescript
- '@expo/require-utils@57.0.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
+ '@expo/require-utils@57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)':
dependencies:
'@babel/code-frame': 7.29.7
'@babel/core': 7.29.7(supports-color@8.1.1)
@@ -13436,22 +13444,22 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@expo/router-server@57.0.2(@expo/metro-runtime@57.0.3)(expo-constants@57.0.3)(expo-font@57.0.0)(expo-router@57.0.4)(expo-server@57.0.0)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(supports-color@8.1.1)':
+ '@expo/router-server@57.0.9(@expo/metro-runtime@57.0.15)(expo-constants@57.0.17)(expo-font@57.0.3)(expo-router@57.0.19)(expo-server@57.0.3)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(supports-color@8.1.1)':
dependencies:
debug: 4.4.3(supports-color@8.1.1)
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-constants: 57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
- expo-font: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- expo-server: 57.0.0
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
+ expo-font: 57.0.3(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo-server: 57.0.3
react: 19.2.3
optionalDependencies:
- '@expo/metro-runtime': 57.0.3(@expo/log-box@57.0.0)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- expo-router: 57.0.4(3bc51b20989ed2c3020a920bb0eeb449)
+ '@expo/metro-runtime': 57.0.15(@expo/log-box@57.0.4)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo-router: 57.0.19(c021d0cd4acfce308650ff4c6746808d)
react-dom: 19.2.3(react@19.2.3)
transitivePeerDependencies:
- supports-color
- '@expo/schema-utils@57.0.1': {}
+ '@expo/schema-utils@57.0.2': {}
'@expo/sdk-runtime-versions@1.0.0': {}
@@ -13461,17 +13469,32 @@ snapshots:
'@expo/sudo-prompt@9.3.2': {}
- '@expo/ui@57.0.7(@babel/core@7.29.7(supports-color@8.1.1))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@expo/ui@57.0.11(@babel/core@7.29.7(supports-color@8.1.1))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
sf-symbols-typescript: 2.2.0
vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
optionalDependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
react-dom: 19.2.3(react@19.2.3)
- react-native-worklets: 0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ react-native-worklets: 0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ transitivePeerDependencies:
+ - '@types/react'
+ - '@types/react-dom'
+
+ '@expo/ui@57.0.16(@babel/core@7.29.7(supports-color@8.1.1))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ dependencies:
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ react: 19.2.3
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ sf-symbols-typescript: 2.2.0
+ vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
+ optionalDependencies:
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ react-dom: 19.2.3(react@19.2.3)
+ react-native-worklets: 0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
transitivePeerDependencies:
- '@types/react'
- '@types/react-dom'
@@ -13540,40 +13563,40 @@ snapshots:
- supports-color
- utf-8-validate
- '@gorhom/bottom-sheet@5.2.14(6e44dcb7dcfecf271936e923703300f2)':
+ '@gorhom/bottom-sheet@5.2.14(50aa6fff1dfa2319f41b6b41f4bd94c3)':
dependencies:
- '@gorhom/portal': 1.0.14(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@gorhom/portal': 1.0.14(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
invariant: 2.2.4
- react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-gesture-handler: 2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react: 19.3.0-canary-96fcba90-20260728
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native-gesture-handler: 2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
optionalDependencies:
'@types/react': 19.2.17
+ optional: true
- '@gorhom/bottom-sheet@5.2.14(da77d43574db60c81967e07362a6f6fb)':
+ '@gorhom/bottom-sheet@5.2.14(b3053a3c63c272f26bedf4b267721ee5)':
dependencies:
- '@gorhom/portal': 1.0.14(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ '@gorhom/portal': 1.0.14(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
invariant: 2.2.4
- react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
- react-native-gesture-handler: 2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
- react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ react: 19.2.3
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native-gesture-handler: 2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
optionalDependencies:
'@types/react': 19.2.17
- optional: true
- '@gorhom/portal@1.0.14(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@gorhom/portal@1.0.14(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
nanoid: 3.3.16
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- '@gorhom/portal@1.0.14(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)':
+ '@gorhom/portal@1.0.14(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)':
dependencies:
nanoid: 3.3.16
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
optional: true
'@hono/node-server@2.0.12(hono@4.12.32)':
@@ -14568,27 +14591,35 @@ snapshots:
optionalDependencies:
'@types/react': 19.2.17
- '@react-native-community/netinfo@12.0.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@react-native-community/netinfo@12.0.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- '@react-native-masked-view/masked-view@0.3.2(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@react-native-masked-view/masked-view@0.3.2(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- '@react-native/assets-registry@0.86.0': {}
+ '@react-native/assets-registry@0.86.2': {}
+
+ '@react-native/babel-plugin-codegen@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
+ dependencies:
+ '@babel/traverse': 7.29.7(supports-color@8.1.1)
+ '@react-native/codegen': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))
+ transitivePeerDependencies:
+ - '@babel/core'
+ - supports-color
- '@react-native/babel-plugin-codegen@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@react-native/babel-plugin-codegen@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
'@babel/traverse': 7.29.7(supports-color@8.1.1)
- '@react-native/codegen': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))
+ '@react-native/codegen': 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))
transitivePeerDependencies:
- '@babel/core'
- supports-color
- '@react-native/babel-preset@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@react-native/babel-preset@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
'@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
@@ -14619,14 +14650,14 @@ snapshots:
'@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
- '@react-native/babel-plugin-codegen': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ '@react-native/babel-plugin-codegen': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
babel-plugin-syntax-hermes-parser: 0.36.0
babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7(supports-color@8.1.1))
react-refresh: 0.14.2
transitivePeerDependencies:
- supports-color
- '@react-native/codegen@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))':
+ '@react-native/codegen@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))':
dependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
'@babel/parser': 7.29.7
@@ -14636,25 +14667,37 @@ snapshots:
tinyglobby: 0.2.17
yargs: 17.7.3
- '@react-native/community-cli-plugin@0.86.0(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@react-native/codegen@0.86.3(@babel/core@7.29.7(supports-color@8.1.1))':
dependencies:
- '@react-native/dev-middleware': 0.86.0(supports-color@8.1.1)
+ '@babel/core': 7.29.7(supports-color@8.1.1)
+ '@babel/parser': 7.29.7
+ hermes-parser: 0.36.0
+ invariant: 2.2.4
+ nullthrows: 1.1.1
+ tinyglobby: 0.2.17
+ yargs: 17.7.3
+
+ '@react-native/community-cli-plugin@0.86.2(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)':
+ dependencies:
+ '@react-native/dev-middleware': 0.86.2(supports-color@8.1.1)
debug: 4.4.3(supports-color@8.1.1)
invariant: 2.2.4
- metro: 0.84.4(supports-color@8.1.1)
- metro-config: 0.84.4(supports-color@8.1.1)
- metro-core: 0.84.4
+ metro: 0.84.5(supports-color@8.1.1)
+ metro-config: 0.84.5(supports-color@8.1.1)
+ metro-core: 0.84.5
semver: 7.8.5
optionalDependencies:
- '@react-native/metro-config': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ '@react-native/metro-config': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
- '@react-native/debugger-frontend@0.86.0': {}
+ '@react-native/debugger-frontend@0.86.2': {}
- '@react-native/debugger-shell@0.86.0(supports-color@8.1.1)':
+ '@react-native/debugger-frontend@0.86.3': {}
+
+ '@react-native/debugger-shell@0.86.2(supports-color@8.1.1)':
dependencies:
cross-spawn: 7.0.6
debug: 4.4.3(supports-color@8.1.1)
@@ -14662,11 +14705,19 @@ snapshots:
transitivePeerDependencies:
- supports-color
- '@react-native/dev-middleware@0.86.0(supports-color@8.1.1)':
+ '@react-native/debugger-shell@0.86.3(supports-color@8.1.1)':
+ dependencies:
+ cross-spawn: 7.0.6
+ debug: 4.4.3(supports-color@8.1.1)
+ fb-dotslash: 0.5.8
+ transitivePeerDependencies:
+ - supports-color
+
+ '@react-native/dev-middleware@0.86.2(supports-color@8.1.1)':
dependencies:
'@isaacs/ttlcache': 1.4.1
- '@react-native/debugger-frontend': 0.86.0
- '@react-native/debugger-shell': 0.86.0(supports-color@8.1.1)
+ '@react-native/debugger-frontend': 0.86.2
+ '@react-native/debugger-shell': 0.86.2(supports-color@8.1.1)
chrome-launcher: 0.15.2(supports-color@8.1.1)
chromium-edge-launcher: 0.3.0(supports-color@8.1.1)
connect: 3.7.0(supports-color@8.1.1)
@@ -14681,25 +14732,44 @@ snapshots:
- supports-color
- utf-8-validate
- '@react-native/gradle-plugin@0.86.0': {}
+ '@react-native/dev-middleware@0.86.3(supports-color@8.1.1)':
+ dependencies:
+ '@isaacs/ttlcache': 1.4.1
+ '@react-native/debugger-frontend': 0.86.3
+ '@react-native/debugger-shell': 0.86.3(supports-color@8.1.1)
+ chrome-launcher: 0.15.2(supports-color@8.1.1)
+ chromium-edge-launcher: 0.3.0(supports-color@8.1.1)
+ connect: 3.7.0(supports-color@8.1.1)
+ debug: 4.4.3(supports-color@8.1.1)
+ invariant: 2.2.4
+ nullthrows: 1.1.1
+ open: 7.4.2
+ serve-static: 1.16.3(supports-color@8.1.1)
+ ws: 7.5.11
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
- '@react-native/js-polyfills@0.86.0': {}
+ '@react-native/gradle-plugin@0.86.2': {}
- '@react-native/metro-babel-transformer@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@react-native/js-polyfills@0.86.2': {}
+
+ '@react-native/metro-babel-transformer@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
- '@react-native/babel-preset': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ '@react-native/babel-preset': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
hermes-parser: 0.36.0
nullthrows: 1.1.1
transitivePeerDependencies:
- supports-color
- '@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
+ '@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)':
dependencies:
- '@react-native/js-polyfills': 0.86.0
- '@react-native/metro-babel-transformer': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
- metro-config: 0.84.4(supports-color@8.1.1)
- metro-runtime: 0.84.4
+ '@react-native/js-polyfills': 0.86.2
+ '@react-native/metro-babel-transformer': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ metro-config: 0.84.5(supports-color@8.1.1)
+ metro-runtime: 0.84.5
transitivePeerDependencies:
- '@babel/core'
- bufferutil
@@ -14710,23 +14780,25 @@ snapshots:
'@react-native/normalize-colors@0.79.7': {}
- '@react-native/normalize-colors@0.86.0': {}
+ '@react-native/normalize-colors@0.86.2': {}
+
+ '@react-native/normalize-colors@0.86.3': {}
- '@react-native/virtualized-lists@0.86.0(@types/react@19.2.17)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
+ '@react-native/virtualized-lists@0.86.2(@types/react@19.2.17)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)':
dependencies:
invariant: 2.2.4
nullthrows: 1.1.1
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
optionalDependencies:
'@types/react': 19.2.17
- '@react-native/virtualized-lists@0.86.0(@types/react@19.2.17)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)':
+ '@react-native/virtualized-lists@0.86.2(@types/react@19.2.17)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)':
dependencies:
invariant: 2.2.4
nullthrows: 1.1.1
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
optionalDependencies:
'@types/react': 19.2.17
@@ -15023,7 +15095,7 @@ snapshots:
- '@opentelemetry/exporter-trace-otlp-http'
- supports-color
- '@sentry/react-native@7.11.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)':
+ '@sentry/react-native@7.11.0(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)':
dependencies:
'@sentry/babel-plugin-component-annotate': 4.8.0
'@sentry/browser': 10.37.0
@@ -15032,9 +15104,9 @@ snapshots:
'@sentry/react': 10.37.0(react@19.2.3)
'@sentry/types': 10.37.0
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
optionalDependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
transitivePeerDependencies:
- encoding
- supports-color
@@ -15465,15 +15537,6 @@ snapshots:
picocolors: 1.1.1
pretty-format: 27.5.1
- '@testing-library/jest-dom@6.9.1':
- dependencies:
- '@adobe/css-tools': 4.5.0
- aria-query: 5.3.2
- css.escape: 1.5.1
- dom-accessibility-api: 0.6.3
- picocolors: 1.1.1
- redent: 3.0.0
-
'@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.3.0-canary-96fcba90-20260728(react@19.3.0-canary-96fcba90-20260728))(react@19.3.0-canary-96fcba90-20260728)':
dependencies:
'@babel/runtime': 7.29.7
@@ -16088,7 +16151,7 @@ snapshots:
agent-base@9.0.0: {}
- agent-cli-detector@0.1.2: {}
+ agent-cli-detector@0.1.7: {}
ajv-formats@3.0.1(ajv@8.20.0):
optionalDependencies:
@@ -16208,8 +16271,6 @@ snapshots:
dependencies:
dequal: 2.0.3
- aria-query@5.3.2: {}
-
asap@2.0.6: {}
asn1js@3.0.10:
@@ -16280,7 +16341,7 @@ snapshots:
transitivePeerDependencies:
- '@babel/core'
- babel-preset-expo@57.0.2(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo@57.0.4)(react-refresh@0.14.2)(supports-color@8.1.1):
+ babel-preset-expo@57.0.10(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo@57.0.20)(react-refresh@0.14.2)(supports-color@8.1.1):
dependencies:
'@babel/generator': 7.29.7
'@babel/helper-module-imports': 7.29.7(supports-color@8.1.1)
@@ -16318,7 +16379,7 @@ snapshots:
'@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
'@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
- '@react-native/babel-plugin-codegen': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ '@react-native/babel-plugin-codegen': 0.86.3(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
babel-plugin-react-compiler: 1.0.0
babel-plugin-react-native-web: 0.21.2
babel-plugin-syntax-hermes-parser: 0.36.1
@@ -16327,7 +16388,7 @@ snapshots:
react-refresh: 0.14.2
optionalDependencies:
'@babel/runtime': 7.29.7
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
transitivePeerDependencies:
- '@babel/core'
- supports-color
@@ -16867,8 +16928,6 @@ snapshots:
css-what@6.2.2: {}
- css.escape@1.5.1: {}
-
csstype@3.2.3: {}
culori@4.0.2: {}
@@ -17181,8 +17240,6 @@ snapshots:
dom-accessibility-api@0.5.16: {}
- dom-accessibility-api@0.6.3: {}
-
dom-serializer@2.0.0:
dependencies:
domelementtype: 2.3.0
@@ -17757,184 +17814,184 @@ snapshots:
expect-type@1.3.0: {}
- expo-apple-authentication@57.0.1(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-apple-authentication@57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-application@57.0.2(expo@57.0.4):
+ expo-application@57.0.2(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
- expo-asset@57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
+ expo-asset@57.0.16(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
dependencies:
- '@expo/image-utils': 0.11.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-constants: 57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
+ '@expo/image-utils': 0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- typescript
- expo-audio@57.0.0(expo-asset@57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-audio@57.0.4(expo-asset@57.0.16(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-asset: 57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-asset: 57.0.16(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-background-task@57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
+ expo-background-task@57.0.16(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-task-manager: 57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-task-manager: 57.0.16(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
transitivePeerDependencies:
- react-native
- expo-camera@57.0.1(@types/emscripten@1.41.5)(expo@57.0.4)(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-camera@57.0.4(@types/emscripten@1.41.5)(expo@57.0.20)(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
barcode-detector: 3.2.1(@types/emscripten@1.41.5)
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
optionalDependencies:
react-native-web: 0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
transitivePeerDependencies:
- '@types/emscripten'
- expo-clipboard@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-clipboard@57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-constants@57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1):
+ expo-constants@57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1):
dependencies:
- '@expo/env': 2.4.1(supports-color@8.1.1)
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ '@expo/env': 2.4.3(supports-color@8.1.1)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- expo-crypto@57.0.1(expo@57.0.4):
+ expo-crypto@57.0.1(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
- expo-dev-client@57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
+ expo-dev-client@57.0.18(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-dev-launcher: 57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
- expo-dev-menu: 57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
- expo-dev-menu-interface: 57.0.0(expo@57.0.4)
- expo-manifests: 57.0.0(expo@57.0.4)
- expo-updates-interface: 57.0.0(expo@57.0.4)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-dev-launcher: 57.0.19(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ expo-dev-menu: 57.0.18(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ expo-dev-menu-interface: 57.0.0(expo@57.0.20)
+ expo-manifests: 57.0.1(expo@57.0.20)
+ expo-updates-interface: 57.0.1(expo@57.0.20)
transitivePeerDependencies:
- react-native
- expo-dev-launcher@57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
+ expo-dev-launcher@57.0.19(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
dependencies:
- '@expo/schema-utils': 57.0.1
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-dev-menu: 57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
- expo-manifests: 57.0.0(expo@57.0.4)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ '@expo/schema-utils': 57.0.2
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-dev-menu: 57.0.18(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ expo-manifests: 57.0.1(expo@57.0.20)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-dev-menu-interface@57.0.0(expo@57.0.4):
+ expo-dev-menu-interface@57.0.0(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
- expo-dev-menu@57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
+ expo-dev-menu@57.0.18(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-dev-menu-interface: 57.0.0(expo@57.0.4)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-dev-menu-interface: 57.0.0(expo@57.0.20)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-device@57.0.0(expo@57.0.4):
+ expo-device@57.0.1(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
ua-parser-js: 0.7.41
- expo-document-picker@57.0.0(expo@57.0.4):
+ expo-document-picker@57.0.1(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
- expo-eas-client@57.0.0: {}
+ expo-eas-client@57.0.3: {}
- expo-file-system@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
+ expo-file-system@57.0.6(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-font@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-font@57.0.3(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
fontfaceobserver: 2.3.0
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-glass-effect@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-glass-effect@57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-haptics@57.0.0(expo@57.0.4):
+ expo-haptics@57.0.2(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
- expo-image-loader@57.0.0(expo@57.0.4):
+ expo-image-loader@57.0.1(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
- expo-image-picker@57.0.2(expo@57.0.4):
+ expo-image-picker@57.0.16(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-image-loader: 57.0.0(expo@57.0.4)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-image-loader: 57.0.1(expo@57.0.20)
- expo-json-utils@57.0.0: {}
+ expo-json-utils@57.0.1: {}
- expo-keep-awake@57.0.0(expo@57.0.4)(react@19.2.3):
+ expo-keep-awake@57.0.1(expo@57.0.20)(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- expo-libghostty@0.8.1(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-libghostty@0.8.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-linking@57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
+ expo-linking@57.0.9(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
dependencies:
- expo-constants: 57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
+ expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
invariant: 2.2.4
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
transitivePeerDependencies:
- expo
- supports-color
- expo-local-authentication@57.0.0(expo@57.0.4):
+ expo-local-authentication@57.0.2(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
invariant: 2.2.4
- expo-localization@57.0.1(expo@57.0.4)(react@19.2.3):
+ expo-localization@57.0.1(expo@57.0.20)(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
rtl-detect: 1.1.2
optional: true
- expo-manifests@57.0.0(expo@57.0.4):
+ expo-manifests@57.0.1(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-json-utils: 57.0.0
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-json-utils: 57.0.1
- expo-modules-autolinking@57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1):
+ expo-modules-autolinking@57.0.12(@typescript/typescript6@6.0.2)(supports-color@8.1.1):
dependencies:
- '@expo/require-utils': 57.0.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/require-utils': 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
'@expo/spawn-async': 1.8.0
chalk: 4.1.2
commander: 7.2.0
@@ -17942,60 +17999,58 @@ snapshots:
- supports-color
- typescript
- expo-modules-core@57.0.3(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-modules-core@57.0.16(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
- '@expo/expo-modules-macros-plugin': 0.3.0
- expo-modules-jsi: 57.0.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ '@expo/expo-modules-macros-plugin': 0.6.1
+ expo-modules-jsi: 57.0.8(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
invariant: 2.2.4
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
optionalDependencies:
- react-native-worklets: 0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ react-native-worklets: 0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
- expo-modules-jsi@57.0.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
+ expo-modules-jsi@57.0.8(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
dependencies:
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-network@57.0.1(expo@57.0.4)(react@19.2.3):
+ expo-network@57.0.1(expo@57.0.20)(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- expo-notifications@57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
+ expo-notifications@57.0.17(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
dependencies:
- '@expo/image-utils': 0.11.1(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/image-utils': 0.11.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
abort-controller: 3.0.0
badgin: 1.2.3
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-application: 57.0.2(expo@57.0.4)
- expo-constants: 57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-application: 57.0.2(expo@57.0.20)
+ expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- typescript
- expo-router@57.0.4(3bc51b20989ed2c3020a920bb0eeb449):
+ expo-router@57.0.19(c021d0cd4acfce308650ff4c6746808d):
dependencies:
- '@expo/log-box': 57.0.0(@expo/dom-webview@57.0.0)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- '@expo/metro-runtime': 57.0.3(@expo/log-box@57.0.0)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- '@expo/schema-utils': 57.0.1
- '@expo/ui': 57.0.7(@babel/core@7.29.7(supports-color@8.1.1))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/metro-runtime': 57.0.15(@expo/log-box@57.0.4)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/schema-utils': 57.0.2
+ '@expo/ui': 57.0.16(@babel/core@7.29.7(supports-color@8.1.1))(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
'@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.3)
'@radix-ui/react-tabs': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- '@testing-library/jest-dom': 6.9.1
- '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
+ '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
client-only: 0.0.1
color: 4.2.3
debug: 4.4.3(supports-color@8.1.1)
escape-string-regexp: 4.0.0
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-constants: 57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
- expo-glass-effect: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- expo-linking: 57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
- expo-server: 57.0.0
- expo-symbols: 57.0.0(expo-font@57.0.0)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
+ expo-glass-effect: 57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo-linking: 57.0.9(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ expo-server: 57.0.3
+ expo-symbols: 57.0.2(expo-font@57.0.3)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
fast-deep-equal: 3.1.3
invariant: 2.2.4
nanoid: 3.3.16
@@ -18003,10 +18058,10 @@ snapshots:
react: 19.2.3
react-fast-compare: 3.2.2
react-is: 19.2.7
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-drawer-layout: 4.2.5(5192597165fff2dd7abc0f37e93a3df3)
- react-native-safe-area-context: 5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-screens: 4.25.2(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native-drawer-layout: 4.2.5(1a764842d23a66d6618895018fb163bf)
+ react-native-safe-area-context: 5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-screens: 4.26.2(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
server-only: 0.0.1
sf-symbols-typescript: 2.2.0
shallowequal: 1.1.0
@@ -18014,132 +18069,131 @@ snapshots:
vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
optionalDependencies:
react-dom: 19.2.3(react@19.2.3)
- react-native-gesture-handler: 2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-gesture-handler: 2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-native-web: 0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
transitivePeerDependencies:
- '@babel/core'
- - '@testing-library/dom'
- '@types/react'
- '@types/react-dom'
- expo-font
- react-native-worklets
- supports-color
- expo-secure-store@57.0.0(expo@57.0.4):
+ expo-secure-store@57.0.3(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
- expo-server@57.0.0: {}
+ expo-server@57.0.3: {}
- expo-sharing@57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
+ expo-sharing@57.0.18(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
dependencies:
- '@expo/config-plugins': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/config-types': 57.0.1
- '@expo/plist': 0.8.0
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-types': 57.0.2
+ '@expo/plist': 0.8.1
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
transitivePeerDependencies:
- supports-color
- typescript
- expo-sqlite@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-sqlite@57.0.2(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
await-lock: 2.2.2
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo-status-bar@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-status-bar@57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
expo-structured-headers@57.0.0: {}
- expo-symbols@57.0.0(expo-font@57.0.0)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ expo-symbols@57.0.2(expo-font@57.0.3)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
'@expo-google-fonts/material-symbols': 0.4.38
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-font: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-font: 57.0.3(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
sf-symbols-typescript: 2.2.0
- expo-task-manager@57.0.2(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
+ expo-task-manager@57.0.16(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- unimodules-app-loader: 57.0.0
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ unimodules-app-loader: 57.0.1
- expo-updates-interface@57.0.0(expo@57.0.4):
+ expo-updates-interface@57.0.1(expo@57.0.20):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
- expo-updates@57.0.6(expo-dev-client@57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)))(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
+ expo-updates@57.0.21(expo-dev-client@57.0.18(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)))(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
dependencies:
'@expo/code-signing-certificates': 0.0.6
- '@expo/plist': 0.8.0
+ '@expo/plist': 0.8.1
'@expo/spawn-async': 1.8.0
arg: 4.1.3
chalk: 4.1.2
debug: 4.4.3(supports-color@8.1.1)
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- expo-eas-client: 57.0.0
- expo-manifests: 57.0.0(expo@57.0.4)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ expo-eas-client: 57.0.3
+ expo-manifests: 57.0.1(expo@57.0.20)
expo-structured-headers: 57.0.0
- expo-updates-interface: 57.0.0(expo@57.0.4)
+ expo-updates-interface: 57.0.1(expo@57.0.20)
getenv: 2.0.0
glob: 13.0.6
ignore: 5.3.2
nullthrows: 1.1.1
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
resolve-from: 5.0.0
optionalDependencies:
- expo-dev-client: 57.0.5(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ expo-dev-client: 57.0.18(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
transitivePeerDependencies:
- supports-color
- expo-web-browser@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
+ expo-web-browser@57.0.2(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)):
dependencies:
- expo: 57.0.4(ffe90122b92e78d11051c8a0633e15fd)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ expo: 57.0.20(a8e7ccc650ea22a90fc5a67384b934a6)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- expo@57.0.4(ffe90122b92e78d11051c8a0633e15fd):
+ expo@57.0.20(a8e7ccc650ea22a90fc5a67384b934a6):
dependencies:
'@babel/runtime': 7.29.7
- '@expo/cli': 57.0.6(@expo/dom-webview@57.0.0)(@expo/metro-runtime@57.0.3)(@typescript/typescript6@6.0.2)(expo-constants@57.0.3)(expo-font@57.0.0)(expo-router@57.0.4)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
- '@expo/config': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/config-plugins': 57.0.3(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/devtools': 57.0.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- '@expo/fingerprint': 0.20.3(supports-color@8.1.1)
- '@expo/local-build-cache-provider': 57.0.2(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- '@expo/log-box': 57.0.0(@expo/dom-webview@57.0.0)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- '@expo/metro': 56.0.0(supports-color@8.1.1)
- '@expo/metro-config': 57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(supports-color@8.1.1)
+ '@expo/cli': 57.0.22(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.15)(@typescript/typescript6@6.0.2)(expo-constants@57.0.17)(expo-font@57.0.3)(expo-router@57.0.19)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ '@expo/config': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/config-plugins': 57.0.9(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/devtools': 57.0.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/fingerprint': 0.20.12(supports-color@8.1.1)
+ '@expo/local-build-cache-provider': 57.0.8(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ '@expo/log-box': 57.0.4(@expo/dom-webview@57.0.1)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/metro': 56.0.2(supports-color@8.1.1)
+ '@expo/metro-config': 57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.20)(supports-color@8.1.1)
'@ungap/structured-clone': 1.3.1
- babel-preset-expo: 57.0.2(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo@57.0.4)(react-refresh@0.14.2)(supports-color@8.1.1)
- expo-asset: 57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
- expo-constants: 57.0.3(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
- expo-file-system: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
- expo-font: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- expo-keep-awake: 57.0.0(expo@57.0.4)(react@19.2.3)
- expo-modules-autolinking: 57.0.5(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
- expo-modules-core: 57.0.3(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ babel-preset-expo: 57.0.10(@babel/core@7.29.7(supports-color@8.1.1))(@babel/runtime@7.29.7)(expo@57.0.20)(react-refresh@0.14.2)(supports-color@8.1.1)
+ expo-asset: 57.0.16(@typescript/typescript6@6.0.2)(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ expo-constants: 57.0.17(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(supports-color@8.1.1)
+ expo-file-system: 57.0.6(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ expo-font: 57.0.3(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo-keep-awake: 57.0.1(expo@57.0.20)(react@19.2.3)
+ expo-modules-autolinking: 57.0.12(@typescript/typescript6@6.0.2)(supports-color@8.1.1)
+ expo-modules-core: 57.0.16(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
pretty-format: 29.7.0
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
react-refresh: 0.14.2
whatwg-url-minimum: 0.1.2
optionalDependencies:
- '@expo/dom-webview': 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- '@expo/metro-runtime': 57.0.3(@expo/log-box@57.0.0)(expo@57.0.4)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/dom-webview': 57.0.1(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@expo/metro-runtime': 57.0.15(@expo/log-box@57.0.4)(expo@57.0.20)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
react-dom: 19.2.3(react@19.2.3)
react-native-web: 0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
- react-native-webview: 13.16.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-webview: 13.16.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
transitivePeerDependencies:
- '@babel/core'
- bufferutil
@@ -18718,7 +18772,7 @@ snapshots:
property-information: 7.2.0
space-separated-tokens: 2.0.2
- hermes-compiler@250829098.0.14: {}
+ hermes-compiler@250829098.0.16: {}
hermes-estree@0.25.1: {}
@@ -18744,35 +18798,35 @@ snapshots:
dependencies:
hermes-estree: 0.36.1
- heroui-native@1.0.6(165eb86db294c7d42ce40e8c9ab66801):
+ heroui-native@1.0.6(5dab09f39b7c1f66cb0e1b4de78ecc64):
dependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-gesture-handler: 2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-safe-area-context: 5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-svg: 15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-worklets: 0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native-gesture-handler: 2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-safe-area-context: 5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-svg: 15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-worklets: 0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
tailwind-merge: 3.6.0
tailwind-variants: 3.3.0(tailwind-merge@3.6.0)(tailwindcss@4.3.3)
optionalDependencies:
- '@gorhom/bottom-sheet': 5.2.14(6e44dcb7dcfecf271936e923703300f2)
- react-native-screens: 4.25.2(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@gorhom/bottom-sheet': 5.2.14(b3053a3c63c272f26bedf4b267721ee5)
+ react-native-screens: 4.26.2(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- heroui-native@1.0.6(63282ea4a5d8a79f137ba1bf529bfa14):
+ heroui-native@1.0.6(759903ba85becadd460e21d1d0660b91):
dependencies:
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
- react-native-gesture-handler: 2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
- react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
- react-native-safe-area-context: 5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
- react-native-svg: 15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
- react-native-worklets: 0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native-gesture-handler: 2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ react-native-safe-area-context: 5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ react-native-svg: 15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ react-native-worklets: 0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
tailwind-merge: 3.6.0
tailwind-variants: 3.3.0(tailwind-merge@3.6.0)(tailwindcss@4.3.3)
optionalDependencies:
- '@gorhom/bottom-sheet': 5.2.14(da77d43574db60c81967e07362a6f6fb)
- react-native-screens: 4.25.2(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ '@gorhom/bottom-sheet': 5.2.14(50aa6fff1dfa2319f41b6b41f4bd94c3)
+ react-native-screens: 4.26.2(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
highlight.js@10.7.3: {}
@@ -18879,10 +18933,6 @@ snapshots:
ignore@7.0.5: {}
- image-size@1.2.1:
- dependencies:
- queue: 6.0.2
-
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
@@ -18899,8 +18949,6 @@ snapshots:
imurmurhash@0.1.4: {}
- indent-string@4.0.0: {}
-
inflight@1.0.6:
dependencies:
once: 1.4.0
@@ -19356,11 +19404,11 @@ snapshots:
lru_map@0.4.1: {}
- lucide-react-native@1.27.0(react-native-svg@15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ lucide-react-native@1.27.0(react-native-svg@15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-svg: 15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native-svg: 15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
lucide-react@1.27.0(react@19.3.0-canary-96fcba90-20260728):
dependencies:
@@ -19630,51 +19678,51 @@ snapshots:
ts-dedent: 2.3.0
uuid: 11.1.1
- metro-babel-transformer@0.84.4(supports-color@8.1.1):
+ metro-babel-transformer@0.84.5(supports-color@8.1.1):
dependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
flow-enums-runtime: 0.0.6
hermes-parser: 0.35.0
- metro-cache-key: 0.84.4
+ metro-cache-key: 0.84.5
nullthrows: 1.1.1
transitivePeerDependencies:
- supports-color
- metro-cache-key@0.84.4:
+ metro-cache-key@0.84.5:
dependencies:
flow-enums-runtime: 0.0.6
- metro-cache@0.84.4(supports-color@8.1.1):
+ metro-cache@0.84.5(supports-color@8.1.1):
dependencies:
exponential-backoff: 3.1.3
flow-enums-runtime: 0.0.6
https-proxy-agent: 7.0.6(supports-color@8.1.1)
- metro-core: 0.84.4
+ metro-core: 0.84.5
transitivePeerDependencies:
- supports-color
- metro-config@0.84.4(supports-color@8.1.1):
+ metro-config@0.84.5(supports-color@8.1.1):
dependencies:
connect: 3.7.0(supports-color@8.1.1)
flow-enums-runtime: 0.0.6
jest-validate: 29.7.0
- metro: 0.84.4(supports-color@8.1.1)
- metro-cache: 0.84.4(supports-color@8.1.1)
- metro-core: 0.84.4
- metro-runtime: 0.84.4
+ metro: 0.84.5(supports-color@8.1.1)
+ metro-cache: 0.84.5(supports-color@8.1.1)
+ metro-core: 0.84.5
+ metro-runtime: 0.84.5
yaml: 2.9.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
- metro-core@0.84.4:
+ metro-core@0.84.5:
dependencies:
flow-enums-runtime: 0.0.6
lodash.throttle: 4.1.1
- metro-resolver: 0.84.4
+ metro-resolver: 0.84.5
- metro-file-map@0.84.4(supports-color@8.1.1):
+ metro-file-map@0.84.5(supports-color@8.1.1):
dependencies:
debug: 4.4.3(supports-color@8.1.1)
fb-watchman: 2.0.2
@@ -19688,46 +19736,46 @@ snapshots:
transitivePeerDependencies:
- supports-color
- metro-minify-terser@0.84.4:
+ metro-minify-terser@0.84.5:
dependencies:
flow-enums-runtime: 0.0.6
terser: 5.48.0
- metro-resolver@0.84.4:
+ metro-resolver@0.84.5:
dependencies:
flow-enums-runtime: 0.0.6
- metro-runtime@0.84.4:
+ metro-runtime@0.84.5:
dependencies:
'@babel/runtime': 7.29.7
flow-enums-runtime: 0.0.6
- metro-source-map@0.84.4(supports-color@8.1.1):
+ metro-source-map@0.84.5(supports-color@8.1.1):
dependencies:
'@babel/traverse': 7.29.7(supports-color@8.1.1)
'@babel/types': 7.29.7
flow-enums-runtime: 0.0.6
invariant: 2.2.4
- metro-symbolicate: 0.84.4(supports-color@8.1.1)
+ metro-symbolicate: 0.84.5(supports-color@8.1.1)
nullthrows: 1.1.1
- ob1: 0.84.4
+ ob1: 0.84.5
source-map: 0.5.7
vlq: 1.0.1
transitivePeerDependencies:
- supports-color
- metro-symbolicate@0.84.4(supports-color@8.1.1):
+ metro-symbolicate@0.84.5(supports-color@8.1.1):
dependencies:
flow-enums-runtime: 0.0.6
invariant: 2.2.4
- metro-source-map: 0.84.4(supports-color@8.1.1)
+ metro-source-map: 0.84.5(supports-color@8.1.1)
nullthrows: 1.1.1
source-map: 0.5.7
vlq: 1.0.1
transitivePeerDependencies:
- supports-color
- metro-transform-plugins@0.84.4(supports-color@8.1.1):
+ metro-transform-plugins@0.84.5(supports-color@8.1.1):
dependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
'@babel/generator': 7.29.7
@@ -19738,27 +19786,27 @@ snapshots:
transitivePeerDependencies:
- supports-color
- metro-transform-worker@0.84.4(supports-color@8.1.1):
+ metro-transform-worker@0.84.5(supports-color@8.1.1):
dependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
'@babel/generator': 7.29.7
'@babel/parser': 7.29.7
'@babel/types': 7.29.7
flow-enums-runtime: 0.0.6
- metro: 0.84.4(supports-color@8.1.1)
- metro-babel-transformer: 0.84.4(supports-color@8.1.1)
- metro-cache: 0.84.4(supports-color@8.1.1)
- metro-cache-key: 0.84.4
- metro-minify-terser: 0.84.4
- metro-source-map: 0.84.4(supports-color@8.1.1)
- metro-transform-plugins: 0.84.4(supports-color@8.1.1)
+ metro: 0.84.5(supports-color@8.1.1)
+ metro-babel-transformer: 0.84.5(supports-color@8.1.1)
+ metro-cache: 0.84.5(supports-color@8.1.1)
+ metro-cache-key: 0.84.5
+ metro-minify-terser: 0.84.5
+ metro-source-map: 0.84.5(supports-color@8.1.1)
+ metro-transform-plugins: 0.84.5(supports-color@8.1.1)
nullthrows: 1.1.1
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
- metro@0.84.4(supports-color@8.1.1):
+ metro@0.84.5(supports-color@8.1.1):
dependencies:
'@babel/code-frame': 7.29.7
'@babel/core': 7.29.7(supports-color@8.1.1)
@@ -19775,23 +19823,22 @@ snapshots:
flow-enums-runtime: 0.0.6
graceful-fs: 4.2.11
hermes-parser: 0.35.0
- image-size: 1.2.1
invariant: 2.2.4
jest-worker: 29.7.0
jsc-safe-url: 0.2.4
lodash.throttle: 4.1.1
- metro-babel-transformer: 0.84.4(supports-color@8.1.1)
- metro-cache: 0.84.4(supports-color@8.1.1)
- metro-cache-key: 0.84.4
- metro-config: 0.84.4(supports-color@8.1.1)
- metro-core: 0.84.4
- metro-file-map: 0.84.4(supports-color@8.1.1)
- metro-resolver: 0.84.4
- metro-runtime: 0.84.4
- metro-source-map: 0.84.4(supports-color@8.1.1)
- metro-symbolicate: 0.84.4(supports-color@8.1.1)
- metro-transform-plugins: 0.84.4(supports-color@8.1.1)
- metro-transform-worker: 0.84.4(supports-color@8.1.1)
+ metro-babel-transformer: 0.84.5(supports-color@8.1.1)
+ metro-cache: 0.84.5(supports-color@8.1.1)
+ metro-cache-key: 0.84.5
+ metro-config: 0.84.5(supports-color@8.1.1)
+ metro-core: 0.84.5
+ metro-file-map: 0.84.5(supports-color@8.1.1)
+ metro-resolver: 0.84.5
+ metro-runtime: 0.84.5
+ metro-source-map: 0.84.5(supports-color@8.1.1)
+ metro-symbolicate: 0.84.5(supports-color@8.1.1)
+ metro-transform-plugins: 0.84.5(supports-color@8.1.1)
+ metro-transform-worker: 0.84.5(supports-color@8.1.1)
mime-types: 3.0.2
nullthrows: 1.1.1
serialize-error: 2.1.0
@@ -20056,8 +20103,6 @@ snapshots:
mimic-response@3.1.0: {}
- min-indent@1.0.1: {}
-
minimatch@10.2.5:
dependencies:
brace-expansion: 5.0.9
@@ -20168,7 +20213,7 @@ snapshots:
multipasta@0.2.8: {}
- multitars@1.0.0: {}
+ multitars@1.0.2: {}
mz@2.7.0:
dependencies:
@@ -20269,7 +20314,7 @@ snapshots:
nullthrows@1.1.1: {}
- ob1@0.84.4:
+ ob1@0.84.5:
dependencies:
flow-enums-runtime: 0.0.6
@@ -20533,17 +20578,17 @@ snapshots:
transitivePeerDependencies:
- preact-render-to-string
- posthog-react-native@4.60.0(expo-application@57.0.2(expo@57.0.4))(expo-device@57.0.0(expo@57.0.4))(expo-file-system@57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)))(expo-localization@57.0.1(expo@57.0.4)(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)):
+ posthog-react-native@4.60.0(expo-application@57.0.2(expo@57.0.20))(expo-device@57.0.1(expo@57.0.20))(expo-file-system@57.0.6(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)))(expo-localization@57.0.1(expo@57.0.20)(react@19.2.3))(react-native-safe-area-context@5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native-svg@15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)):
dependencies:
'@posthog/core': 1.46.1
'@posthog/types': 1.399.0
optionalDependencies:
- expo-application: 57.0.2(expo@57.0.4)
- expo-device: 57.0.0(expo@57.0.4)
- expo-file-system: 57.0.0(expo@57.0.4)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
- expo-localization: 57.0.1(expo@57.0.4)(react@19.2.3)
- react-native-safe-area-context: 5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-svg: 15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ expo-application: 57.0.2(expo@57.0.20)
+ expo-device: 57.0.1(expo@57.0.20)
+ expo-file-system: 57.0.6(expo@57.0.20)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))
+ expo-localization: 57.0.1(expo@57.0.20)(react@19.2.3)
+ react-native-safe-area-context: 5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-svg: 15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
postject@1.0.0-alpha.6:
dependencies:
@@ -20690,10 +20735,6 @@ snapshots:
queue-microtask@1.2.3: {}
- queue@6.0.2:
- dependencies:
- inherits: 2.0.4
-
quick-format-unescaped@4.0.4: {}
quick-lru@5.1.1: {}
@@ -20766,105 +20807,105 @@ snapshots:
react-is@19.2.7: {}
- react-native-drawer-layout@4.2.5(5192597165fff2dd7abc0f37e93a3df3):
+ react-native-drawer-layout@4.2.5(1a764842d23a66d6618895018fb163bf):
dependencies:
color: 4.2.3
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-gesture-handler: 2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native-gesture-handler: 2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
use-latest-callback: 0.2.6(react@19.2.3)
- react-native-gesture-handler@2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ react-native-gesture-handler@2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
'@egjs/hammerjs': 2.0.17
'@types/react-test-renderer': 19.1.0
hoist-non-react-statics: 3.3.2
invariant: 2.2.4
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-gesture-handler@2.32.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
+ react-native-gesture-handler@2.32.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
dependencies:
'@egjs/hammerjs': 2.0.17
'@types/react-test-renderer': 19.1.0
hoist-non-react-statics: 3.3.2
invariant: 2.2.4
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
- react-native-is-edge-to-edge@1.3.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ react-native-is-edge-to-edge@1.3.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-is-edge-to-edge@1.3.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
+ react-native-is-edge-to-edge@1.3.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
dependencies:
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
- react-native-keyboard-controller@1.21.9(react-native-reanimated@4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ react-native-keyboard-controller@1.21.9(react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-is-edge-to-edge: 1.3.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native-is-edge-to-edge: 1.3.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-reanimated: 4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-reanimated@4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-is-edge-to-edge: 1.3.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
- react-native-worklets: 0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native-is-edge-to-edge: 1.3.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ react-native-worklets: 0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1)
semver: 7.8.5
- react-native-reanimated@4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
+ react-native-reanimated@4.5.1(react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
dependencies:
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
- react-native-is-edge-to-edge: 1.3.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
- react-native-worklets: 0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native-is-edge-to-edge: 1.3.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ react-native-worklets: 0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
semver: 7.8.5
- react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ react-native-safe-area-context@5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
+ react-native-safe-area-context@5.7.0(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
dependencies:
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
- react-native-screens@4.25.2(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ react-native-screens@4.26.2(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
react: 19.2.3
react-freeze: 1.0.4(react@19.2.3)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
warn-once: 0.1.1
- react-native-screens@4.25.2(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
+ react-native-screens@4.26.2(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
dependencies:
react: 19.3.0-canary-96fcba90-20260728
react-freeze: 1.0.4(react@19.3.0-canary-96fcba90-20260728)
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
warn-once: 0.1.1
optional: true
- react-native-svg@15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ react-native-svg@15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
css-select: 5.2.2
css-tree: 1.1.3
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
warn-once: 0.1.1
- react-native-svg@15.15.4(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
+ react-native-svg@15.15.4(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728):
dependencies:
css-select: 5.2.2
css-tree: 1.1.3
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
warn-once: 0.1.1
react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3):
@@ -20882,14 +20923,14 @@ snapshots:
transitivePeerDependencies:
- encoding
- react-native-webview@13.16.1(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
+ react-native-webview@13.16.1(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3):
dependencies:
escape-string-regexp: 4.0.0
invariant: 2.2.4
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
- react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
+ react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(supports-color@8.1.1):
dependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
'@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
@@ -20902,15 +20943,15 @@ snapshots:
'@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
'@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/types': 7.29.7
- '@react-native/metro-config': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ '@react-native/metro-config': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
convert-source-map: 2.0.0
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
semver: 7.8.5
transitivePeerDependencies:
- supports-color
- react-native-worklets@0.10.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1):
+ react-native-worklets@0.10.1(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1):
dependencies:
'@babel/core': 7.29.7(supports-color@8.1.1)
'@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
@@ -20923,23 +20964,23 @@ snapshots:
'@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))
'@babel/preset-typescript': 7.29.7(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
'@babel/types': 7.29.7
- '@react-native/metro-config': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
+ '@react-native/metro-config': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1)
convert-source-map: 2.0.0
react: 19.3.0-canary-96fcba90-20260728
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1)
semver: 7.8.5
transitivePeerDependencies:
- supports-color
- react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1):
+ react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1):
dependencies:
- '@react-native/assets-registry': 0.86.0
- '@react-native/codegen': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))
- '@react-native/community-cli-plugin': 0.86.0(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)
- '@react-native/gradle-plugin': 0.86.0
- '@react-native/js-polyfills': 0.86.0
- '@react-native/normalize-colors': 0.86.0
- '@react-native/virtualized-lists': 0.86.0(@types/react@19.2.17)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
+ '@react-native/assets-registry': 0.86.2
+ '@react-native/codegen': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))
+ '@react-native/community-cli-plugin': 0.86.2(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)
+ '@react-native/gradle-plugin': 0.86.2
+ '@react-native/js-polyfills': 0.86.2
+ '@react-native/normalize-colors': 0.86.2
+ '@react-native/virtualized-lists': 0.86.2(@types/react@19.2.17)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)
abort-controller: 3.0.0
anser: 1.4.10
ansi-regex: 5.0.1
@@ -20947,11 +20988,11 @@ snapshots:
base64-js: 1.5.1
commander: 12.1.0
flow-enums-runtime: 0.0.6
- hermes-compiler: 250829098.0.14
+ hermes-compiler: 250829098.0.16
invariant: 2.2.4
memoize-one: 5.2.1
- metro-runtime: 0.84.4
- metro-source-map: 0.84.4(supports-color@8.1.1)
+ metro-runtime: 0.84.5
+ metro-source-map: 0.84.5(supports-color@8.1.1)
nullthrows: 1.1.1
pretty-format: 29.7.0
promise: 8.3.0
@@ -20976,15 +21017,15 @@ snapshots:
- supports-color
- utf-8-validate
- react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1):
+ react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1):
dependencies:
- '@react-native/assets-registry': 0.86.0
- '@react-native/codegen': 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))
- '@react-native/community-cli-plugin': 0.86.0(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)
- '@react-native/gradle-plugin': 0.86.0
- '@react-native/js-polyfills': 0.86.0
- '@react-native/normalize-colors': 0.86.0
- '@react-native/virtualized-lists': 0.86.0(@types/react@19.2.17)(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
+ '@react-native/assets-registry': 0.86.2
+ '@react-native/codegen': 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))
+ '@react-native/community-cli-plugin': 0.86.2(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(supports-color@8.1.1)
+ '@react-native/gradle-plugin': 0.86.2
+ '@react-native/js-polyfills': 0.86.2
+ '@react-native/normalize-colors': 0.86.2
+ '@react-native/virtualized-lists': 0.86.2(@types/react@19.2.17)(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.3.0-canary-96fcba90-20260728)(supports-color@8.1.1))(react@19.3.0-canary-96fcba90-20260728)
abort-controller: 3.0.0
anser: 1.4.10
ansi-regex: 5.0.1
@@ -20992,11 +21033,11 @@ snapshots:
base64-js: 1.5.1
commander: 12.1.0
flow-enums-runtime: 0.0.6
- hermes-compiler: 250829098.0.14
+ hermes-compiler: 250829098.0.16
invariant: 2.2.4
memoize-one: 5.2.1
- metro-runtime: 0.84.4
- metro-source-map: 0.84.4(supports-color@8.1.1)
+ metro-runtime: 0.84.5
+ metro-source-map: 0.84.5(supports-color@8.1.1)
nullthrows: 1.1.1
pretty-format: 29.7.0
promise: 8.3.0
@@ -21090,11 +21131,6 @@ snapshots:
real-require@1.0.0: {}
- redent@3.0.0:
- dependencies:
- indent-string: 4.0.0
- strip-indent: 3.0.0
-
redis-errors@1.2.0: {}
redis-parser@3.0.0:
@@ -21382,6 +21418,8 @@ snapshots:
safer-buffer@2.1.2: {}
+ sandbox-cli-detector@0.2.0: {}
+
sanitize-filename@1.6.4:
dependencies:
truncate-utf8-bytes: 1.0.2
@@ -21751,10 +21789,6 @@ snapshots:
dependencies:
ansi-regex: 6.2.2
- strip-indent@3.0.0:
- dependencies:
- min-indent: 1.0.1
-
strip-json-comments@2.0.1:
optional: true
@@ -22141,7 +22175,7 @@ snapshots:
trough: 2.2.0
vfile: 6.0.3
- unimodules-app-loader@57.0.0: {}
+ unimodules-app-loader@57.0.1: {}
unist-util-is@6.0.1:
dependencies:
@@ -22170,20 +22204,20 @@ snapshots:
universalify@2.0.1: {}
- uniwind@1.10.0(@expo/metro-config@57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(supports-color@8.1.1))(metro-cache@0.84.4(supports-color@8.1.1))(metro-transform-worker@0.84.4(supports-color@8.1.1))(metro@0.84.4(supports-color@8.1.1))(react-native@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(tailwindcss@4.3.3):
+ uniwind@1.10.0(@expo/metro-config@57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.20)(supports-color@8.1.1))(metro-cache@0.84.5(supports-color@8.1.1))(metro-transform-worker@0.84.5(supports-color@8.1.1))(metro@0.84.5(supports-color@8.1.1))(react-native@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1))(react@19.2.3)(tailwindcss@4.3.3):
dependencies:
'@tailwindcss/node': 4.3.0
'@tailwindcss/oxide': 4.3.0
culori: 4.0.2
lightningcss: 1.30.1
- metro: 0.84.4(supports-color@8.1.1)
- metro-cache: 0.84.4(supports-color@8.1.1)
+ metro: 0.84.5(supports-color@8.1.1)
+ metro-cache: 0.84.5(supports-color@8.1.1)
react: 19.2.3
- react-native: 0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.0(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
+ react-native: 0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(@react-native/metro-config@0.86.2(@babel/core@7.29.7(supports-color@8.1.1))(supports-color@8.1.1))(@types/react@19.2.17)(react@19.2.3)(supports-color@8.1.1)
tailwindcss: 4.3.3
optionalDependencies:
- '@expo/metro-config': 57.0.3(@typescript/typescript6@6.0.2)(expo@57.0.4)(supports-color@8.1.1)
- metro-transform-worker: 0.84.4(supports-color@8.1.1)
+ '@expo/metro-config': 57.0.12(@typescript/typescript6@6.0.2)(expo@57.0.20)(supports-color@8.1.1)
+ metro-transform-worker: 0.84.5(supports-color@8.1.1)
unpipe@1.0.0: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 7946137a7..99fa53eb9 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -136,24 +136,24 @@ minimumReleaseAgeExclude:
- shiki@4.3.0
- "@expo/metro-runtime@56.0.16"
- "@expo/ui@56.0.19"
- - expo-constants@56.0.19
- - expo-router@56.2.12
+ - expo-constants@56.0.19 || 57.0.12
+ - expo-router@56.2.12 || 57.0.14
- foxts
- uniwind@1.10.0
- - "@expo/cli@56.1.17"
+ - '@expo/cli@56.1.17 || 57.0.16'
- "@expo/config-plugins@56.0.10"
- "@expo/config-types@56.0.7"
- - "@expo/config@56.0.10"
- - "@expo/fingerprint@0.19.5"
- - "@expo/log-box@56.0.14"
+ - '@expo/config@56.0.10 || 57.0.8'
+ - '@expo/fingerprint@0.19.5 || 0.20.8'
+ - '@expo/log-box@56.0.14 || 57.0.3'
- "@expo/metro-config@56.0.15"
- "@expo/prebuild-config@56.0.17"
- "@expo/router-server@56.0.15"
- babel-preset-expo@56.0.16
- - expo-asset@56.0.18
+ - expo-asset@56.0.18 || 57.0.12
- expo-modules-autolinking@56.0.17
- expo-modules-core@56.0.18
- - expo@56.0.13
+ - expo@56.0.13 || 57.0.14
- uuid@11.1.1
- esbuild@0.28.1
- ws@8.21.0
@@ -193,6 +193,16 @@ minimumReleaseAgeExclude:
- restty@0.2.3 || 0.2.5
- text-shaper@0.1.26 || 0.1.27
- scheduler@0.28.0-canary-96fcba90-20260728
+ - '@expo/inline-modules@0.1.6'
+ - expo-background-task@57.0.11
+ - expo-dev-client@57.0.13
+ - expo-dev-launcher@57.0.13
+ - expo-dev-menu@57.0.13
+ - expo-image-picker@57.0.11
+ - expo-notifications@57.0.12
+ - expo-sharing@57.0.13
+ - expo-task-manager@57.0.11
+ - expo-updates@57.0.15
overrides:
# GHSA-67mh-4wv8-2f99: advisory metadata claims 0.24.3, but esbuild 0.24.x