some key stuff

This commit is contained in:
EthanShoeDev
2025-09-12 01:37:17 -04:00
parent 92882e276e
commit d9879d7a87
8 changed files with 509 additions and 431 deletions

View File

@@ -1,3 +0,0 @@
https://github.com/dylankenneally/react-native-ssh-sftp
https://xtermjs.org/
https://docs.expo.dev/versions/latest/sdk/webview/

View File

@@ -33,20 +33,20 @@
"@react-navigation/native": "^7.1.8", "@react-navigation/native": "^7.1.8",
"@tanstack/react-form": "^1.19.5", "@tanstack/react-form": "^1.19.5",
"@tanstack/react-query": "^5.87.1", "@tanstack/react-query": "^5.87.1",
"expo": "54.0.1", "expo": "54.0.2",
"expo-build-properties": "~1.0.7", "expo-build-properties": "~1.0.8",
"expo-constants": "~18.0.8", "expo-constants": "~18.0.8",
"expo-font": "~14.0.7", "expo-font": "~14.0.8",
"expo-haptics": "~15.0.6", "expo-haptics": "~15.0.7",
"expo-image": "~3.0.7", "expo-image": "~3.0.8",
"expo-linking": "~8.0.7", "expo-linking": "~8.0.8",
"expo-router": "6.0.0", "expo-router": "6.0.1",
"expo-secure-store": "~15.0.6", "expo-secure-store": "~15.0.7",
"expo-splash-screen": "~31.0.8", "expo-splash-screen": "~31.0.9",
"expo-status-bar": "~3.0.7", "expo-status-bar": "~3.0.8",
"expo-symbols": "~1.0.6", "expo-symbols": "~1.0.7",
"expo-system-ui": "~6.0.7", "expo-system-ui": "~6.0.7",
"expo-web-browser": "~15.0.6", "expo-web-browser": "~15.0.7",
"react": "19.1.0", "react": "19.1.0",
"react-dom": "19.1.0", "react-dom": "19.1.0",
"react-native": "0.81.4", "react-native": "0.81.4",

View File

@@ -1,11 +1,18 @@
import SSHClient, { PtyType } from '@dylankenneally/react-native-ssh-sftp'; import SSHClient, { PtyType } from '@dylankenneally/react-native-ssh-sftp';
import { Picker } from '@react-native-picker/picker'; // Removed inline Picker usage in favor of modal selection
import { useStore } from '@tanstack/react-form'; import { useStore } from '@tanstack/react-form';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'expo-router'; import { useRouter } from 'expo-router';
import React from 'react'; import React from 'react';
import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'; import {
import { useAppForm, withFieldGroup } from '../components/form-components'; Pressable,
ScrollView,
StyleSheet,
Text,
View,
Switch,
} from 'react-native';
import { useAppForm, useFieldContext } from '../components/form-components';
import { KeyManagerModal } from '../components/key-manager-modal'; import { KeyManagerModal } from '../components/key-manager-modal';
import { import {
type ConnectionDetails, type ConnectionDetails,
@@ -26,44 +33,57 @@ const defaultValues: ConnectionDetails = {
export default function Index() { export default function Index() {
const router = useRouter(); const router = useRouter();
const storedConnectionsQuery = useQuery( // const storedConnectionsQuery = useQuery(
secretsManager.connections.query.list, // secretsManager.connections.query.list,
); // );
const preferredStoredConnection = storedConnectionsQuery.data?.[0];
const connectionForm = useAppForm({ const connectionForm = useAppForm({
// https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values // https://tanstack.com/form/latest/docs/framework/react/guides/async-initial-values
defaultValues: preferredStoredConnection defaultValues,
? preferredStoredConnection.value
: defaultValues,
validators: { validators: {
onChange: connectionDetailsSchema, onChange: connectionDetailsSchema,
onSubmitAsync: async ({ value }) => { onSubmitAsync: async ({ value }) => {
try { try {
console.log('Connecting to SSH server...'); console.log('Connecting to SSH server...');
const effective = await (async () => {
if (value.security.type === 'password') return value;
if (value.security.keyId) return value;
const keys =
await secretsManager.keys.utils.listEntriesWithValues();
const def = keys.find((k) => k.metadata?.isDefault);
const pick = def ?? keys[0];
if (pick) {
return {
...value,
security: { type: 'key', keyId: pick.id },
} as ConnectionDetails;
}
return value;
})();
const sshClientConnection = await (async () => { const sshClientConnection = await (async () => {
if (value.security.type === 'password') { if (effective.security.type === 'password') {
return await SSHClient.connectWithPassword( return await SSHClient.connectWithPassword(
value.host, effective.host,
value.port, effective.port,
value.username, effective.username,
value.security.password, effective.security.password,
); );
} }
const privateKey = await secretsManager.keys.utils.getPrivateKey( const privateKey = await secretsManager.keys.utils.getPrivateKey(
value.security.keyId, effective.security.keyId,
); );
return await SSHClient.connectWithKey( return await SSHClient.connectWithKey(
value.host, effective.host,
value.port, effective.port,
value.username, effective.username,
privateKey.value, privateKey.value,
); );
})(); })();
await secretsManager.connections.utils.upsertConnection({ await secretsManager.connections.utils.upsertConnection({
id: 'default', id: 'default',
details: value, details: effective,
priority: 0, priority: 0,
}); });
await sshClientConnection.startShell(PtyType.XTERM); await sshClientConnection.startShell(PtyType.XTERM);
@@ -143,10 +163,18 @@ export default function Index() {
</connectionForm.AppField> </connectionForm.AppField>
<connectionForm.AppField name="security.type"> <connectionForm.AppField name="security.type">
{(field) => ( {(field) => (
<field.PickerField label="Security Type"> <View style={styles.inputGroup}>
<Picker.Item label="Password" value="password" /> <Text style={styles.label}>Use Private Key</Text>
<Picker.Item label="Key" value="key" /> <View>
</field.PickerField> {/* Map boolean switch to discriminated union */}
<Switch
value={field.state.value === 'key'}
onValueChange={(val) => {
field.handleChange(val ? 'key' : 'password');
}}
/>
</View>
</View>
)} )}
</connectionForm.AppField> </connectionForm.AppField>
{securityType === 'password' ? ( {securityType === 'password' ? (
@@ -161,13 +189,9 @@ export default function Index() {
)} )}
</connectionForm.AppField> </connectionForm.AppField>
) : ( ) : (
<KeyPairSection <connectionForm.AppField name="security.keyId">
form={connectionForm} {() => <KeyIdPicker />}
fields={{ </connectionForm.AppField>
keyId: 'security.keyId',
type: 'security.type',
}}
/>
)} )}
<View style={styles.actions}> <View style={styles.actions}>
@@ -209,68 +233,60 @@ export default function Index() {
); );
} }
// Yes, HOCs are weird. Its what the docs recommend. function KeyIdPicker() {
// https://tanstack.com/form/v1/docs/framework/react/guides/form-composition#withform-faq const field = useFieldContext<string>();
const KeyPairSection = withFieldGroup({ const hasInteractedRef = React.useRef(false);
defaultValues: { const [manualVisible, setManualVisible] = React.useState(false);
type: 'key',
keyId: '',
},
props: {},
render: function Render({ group }) {
const listPrivateKeysQuery = useQuery(secretsManager.keys.query.list);
const [showManager, setShowManager] = React.useState(false);
return ( const listPrivateKeysQuery = useQuery(secretsManager.keys.query.list);
<group.AppField name="keyId"> const defaultPick = React.useMemo(() => {
{(field) => { const keys = listPrivateKeysQuery.data ?? [];
if (listPrivateKeysQuery.isLoading) { const def = keys.find((k) => k.metadata?.isDefault);
return <Text style={styles.mutedText}>Loading keys...</Text>; return def ?? keys[0];
} }, [listPrivateKeysQuery.data]);
if (listPrivateKeysQuery.isError) { const keys = listPrivateKeysQuery.data ?? [];
return (
<Text style={styles.errorText}> const computedSelectedId = field.state.value ?? defaultPick?.id;
Error: {listPrivateKeysQuery.error.message} const selected = keys.find((k) => k.id === computedSelectedId);
</Text> const display = selected ? (selected.metadata?.label ?? selected.id) : 'None';
);
} const isEmpty = (listPrivateKeysQuery.data?.length ?? 0) === 0;
return ( const visible = manualVisible || (!hasInteractedRef.current && isEmpty);
<>
<field.PickerField label="Key"> return (
{listPrivateKeysQuery.data?.map((key) => { <>
const label = `${key.metadata?.label ?? key.id}${ <View style={styles.inputGroup}>
key.metadata?.isDefault ? ' • Default' : '' <Text style={styles.label}>Private Key</Text>
}`; <Pressable
return ( style={[styles.input, { justifyContent: 'center' }]}
<Picker.Item key={key.id} label={label} value={key.id} /> onPress={() => {
); hasInteractedRef.current = true;
})} setManualVisible(true);
</field.PickerField> }}
<Pressable >
style={styles.secondaryButton} <Text style={{ color: '#E5E7EB' }}>{display}</Text>
onPress={() => setShowManager(true)} </Pressable>
> {!selected && (
<Text style={styles.secondaryButtonText}>Manage Keys</Text> <Text style={styles.mutedText}>
</Pressable> Open Key Manager to add/select a key
<KeyManagerModal </Text>
visible={showManager} )}
onClose={() => { </View>
setShowManager(false); <KeyManagerModal
if (!field.state.value && listPrivateKeysQuery.data) { visible={visible}
const def = listPrivateKeysQuery.data.find( selectedKeyId={computedSelectedId}
(k) => k.metadata?.isDefault, onSelect={(id) => {
); hasInteractedRef.current = true;
if (def) field.handleChange(def.id); field.handleChange(id);
}
}}
/>
</>
);
}} }}
</group.AppField> onClose={() => {
); hasInteractedRef.current = true;
}, setManualVisible(false);
}); }}
/>
</>
);
}
function PreviousConnectionsSection(props: { function PreviousConnectionsSection(props: {
onSelect: (connection: ConnectionDetails) => void; onSelect: (connection: ConnectionDetails) => void;

View File

@@ -14,23 +14,25 @@ import { secretsManager } from '../lib/secrets-manager';
export function KeyManagerModal(props: { export function KeyManagerModal(props: {
visible: boolean; visible: boolean;
onClose: () => void; onClose: () => void;
selectedKeyId?: string;
onSelect?: (keyId: string) => void;
}) { }) {
const listKeysQuery = useQuery(secretsManager.keys.query.list); const listKeysQuery = useQuery(secretsManager.keys.query.list);
const generateMutation = useMutation({ const generateMutation = useMutation({
mutationFn: async () => { mutationFn: async () => {
const id = `key_${Date.now()}`; const id = `key_${Date.now()}`;
const pair = await secretsManager.keys.utils.generateKeyPair({ const pair = await secretsManager.keys.utils.generateKeyPair({
type: 'rsa', type: 'rsa',
keySize: 4096, keySize: 4096,
}); });
await secretsManager.keys.utils.upsertPrivateKey({ await secretsManager.keys.utils.upsertPrivateKey({
keyId: id, keyId: id,
metadata: { priority: 0, label: 'New Key', isDefault: false }, metadata: { priority: 0, label: 'New Key', isDefault: false },
value: pair.privateKey, value: pair.privateKey,
}); });
}, },
}); });
async function handleDelete(keyId: string) { async function handleDelete(keyId: string) {
await secretsManager.keys.utils.deletePrivateKey(keyId); await secretsManager.keys.utils.deletePrivateKey(keyId);
@@ -54,7 +56,7 @@ export function KeyManagerModal(props: {
} }
async function handleGenerate() { async function handleGenerate() {
await generateMutation.mutateAsync(); await generateMutation.mutateAsync();
} }
return ( return (
@@ -68,20 +70,20 @@ export function KeyManagerModal(props: {
</Pressable> </Pressable>
</View> </View>
<Pressable <Pressable
style={[ style={[
styles.primaryButton, styles.primaryButton,
generateMutation.isPending && { opacity: 0.7 }, generateMutation.isPending && { opacity: 0.7 },
]} ]}
disabled={generateMutation.isPending} disabled={generateMutation.isPending}
onPress={handleGenerate} onPress={handleGenerate}
> >
<Text style={styles.primaryButtonText}> <Text style={styles.primaryButtonText}>
{generateMutation.isPending {generateMutation.isPending
? 'Generating…' ? 'Generating…'
: 'Generate New RSA 4096 Key'} : 'Generate New RSA 4096 Key'}
</Text> </Text>
</Pressable> </Pressable>
{listKeysQuery.isLoading ? ( {listKeysQuery.isLoading ? (
<View style={styles.centerRow}> <View style={styles.centerRow}>
@@ -96,6 +98,11 @@ export function KeyManagerModal(props: {
<KeyRow <KeyRow
key={k.id} key={k.id}
entry={k} entry={k}
selected={props.selectedKeyId === k.id}
onSelect={() => {
if (props.onSelect) props.onSelect(k.id);
props.onClose();
}}
onDelete={() => handleDelete(k.id)} onDelete={() => handleDelete(k.id)}
onSetDefault={() => handleSetDefault(k.id)} onSetDefault={() => handleSetDefault(k.id)}
/> />
@@ -114,34 +121,36 @@ function KeyRow(props: {
entry: Awaited< entry: Awaited<
ReturnType<typeof secretsManager.keys.utils.listEntriesWithValues> ReturnType<typeof secretsManager.keys.utils.listEntriesWithValues>
>[number]; >[number];
selected?: boolean;
onSelect?: () => void;
onDelete: () => void; onDelete: () => void;
onSetDefault: () => void; onSetDefault: () => void;
}) { }) {
const [isEditing, setIsEditing] = React.useState(false); const [isEditing, setIsEditing] = React.useState(false);
const [label, setLabel] = React.useState(props.entry.metadata?.label ?? ''); const [label, setLabel] = React.useState(props.entry.metadata?.label ?? '');
const isDefault = props.entry.metadata?.isDefault; const isDefault = props.entry.metadata?.isDefault;
const renameMutation = useMutation({ const renameMutation = useMutation({
mutationFn: async (newLabel: string) => { mutationFn: async (newLabel: string) => {
await secretsManager.keys.utils.upsertPrivateKey({ await secretsManager.keys.utils.upsertPrivateKey({
keyId: props.entry.id, keyId: props.entry.id,
value: props.entry.value, value: props.entry.value,
metadata: { metadata: {
priority: props.entry.metadata.priority, priority: props.entry.metadata.priority,
label: newLabel, label: newLabel,
isDefault: props.entry.metadata.isDefault, isDefault: props.entry.metadata.isDefault,
}, },
}); });
}, },
onSuccess: () => setIsEditing(false), onSuccess: () => setIsEditing(false),
}); });
async function saveLabel() { async function saveLabel() {
await renameMutation.mutateAsync(label); await renameMutation.mutateAsync(label);
} }
return ( return (
<View style={styles.row}> <Pressable style={styles.row} onPress={props.onSelect}>
<View style={{ flex: 1, marginRight: 8 }}> <View style={{ flex: 1, marginRight: 8 }}>
<Text style={styles.rowTitle}> <Text style={styles.rowTitle}>
{(props.entry.metadata?.label ?? props.entry.id) + {(props.entry.metadata?.label ?? props.entry.id) +
@@ -159,6 +168,13 @@ function KeyRow(props: {
) : null} ) : null}
</View> </View>
<View style={styles.rowActions}> <View style={styles.rowActions}>
{props.onSelect ? (
<View style={styles.radioWrap}>
<View
style={[styles.radio, props.selected && styles.radioSelected]}
/>
</View>
) : null}
{!isDefault ? ( {!isDefault ? (
<Pressable <Pressable
style={styles.secondaryButton} style={styles.secondaryButton}
@@ -167,32 +183,32 @@ function KeyRow(props: {
<Text style={styles.secondaryButtonText}>Set Default</Text> <Text style={styles.secondaryButtonText}>Set Default</Text>
</Pressable> </Pressable>
) : null} ) : null}
{isEditing ? ( {isEditing ? (
<Pressable <Pressable
style={[ style={[
styles.secondaryButton, styles.secondaryButton,
renameMutation.isPending && { opacity: 0.6 }, renameMutation.isPending && { opacity: 0.6 },
]} ]}
onPress={saveLabel} onPress={saveLabel}
disabled={renameMutation.isPending} disabled={renameMutation.isPending}
> >
<Text style={styles.secondaryButtonText}> <Text style={styles.secondaryButtonText}>
{renameMutation.isPending ? 'Saving…' : 'Save'} {renameMutation.isPending ? 'Saving…' : 'Save'}
</Text> </Text>
</Pressable> </Pressable>
) : ( ) : (
<Pressable <Pressable
style={styles.secondaryButton} style={styles.secondaryButton}
onPress={() => setIsEditing(true)} onPress={() => setIsEditing(true)}
> >
<Text style={styles.secondaryButtonText}>Rename</Text> <Text style={styles.secondaryButtonText}>Rename</Text>
</Pressable> </Pressable>
)} )}
<Pressable style={styles.dangerButton} onPress={props.onDelete}> <Pressable style={styles.dangerButton} onPress={props.onDelete}>
<Text style={styles.dangerButtonText}>Delete</Text> <Text style={styles.dangerButtonText}>Delete</Text>
</Pressable> </Pressable>
</View> </View>
</View> </Pressable>
); );
} }
@@ -292,6 +308,23 @@ const styles = StyleSheet.create({
gap: 6, gap: 6,
alignItems: 'flex-end', alignItems: 'flex-end',
}, },
radioWrap: {
justifyContent: 'center',
alignItems: 'center',
marginRight: 6,
},
radio: {
width: 16,
height: 16,
borderRadius: 16,
borderColor: '#2A3655',
borderWidth: 2,
backgroundColor: 'transparent',
},
radioSelected: {
backgroundColor: '#2563EB',
borderColor: '#2563EB',
},
secondaryButton: { secondaryButton: {
backgroundColor: 'transparent', backgroundColor: 'transparent',
borderWidth: 1, borderWidth: 1,

View File

@@ -1,47 +1,64 @@
# Key Management UX & Tech Plan # Key Management UX & Tech Plan
Goal: Make SSH private key management clear and consistent: selecting a key, generating/importing keys, renaming, deleting, and setting a default — all with a unified, styled UI and predictable data semantics. Goal: Make SSH private key management clear and consistent: selecting a key,
generating/importing keys, renaming, deleting, and setting a default — all with
a unified, styled UI and predictable data semantics.
## Current State (Summary) ## Current State (Summary)
- Security type uses a `Picker` in the main form; defaults to `password`. - Security type uses a `Picker` in the main form; defaults to `password`.
- Switching to key auth shows a `Picker` for keys (awkward empty state) and a “Manage Keys” button. - Switching to key auth shows a `Picker` for keys (awkward empty state) and a
“Manage Keys” button.
- Keys can be generated, renamed, deleted, set as default inside a modal. - Keys can be generated, renamed, deleted, set as default inside a modal.
- Storage uses a chunked manifest in `expo-secure-store` (works for >2KB values) with metadata (`priority`, `createdAtMs`, `label?`, `isDefault?`). - Storage uses a chunked manifest in `expo-secure-store` (works for >2KB values)
- All key mutations go through one `upsertPrivateKey` that invalidates the React Query `keys` query. with metadata (`priority`, `createdAtMs`, `label?`, `isDefault?`).
- All key mutations go through one `upsertPrivateKey` that invalidates the React
Query `keys` query.
## UX Objectives ## UX Objectives
- Remove the inline key `Picker`; select a key within the Key Manager modal. - Remove the inline key `Picker`; select a key within the Key Manager modal.
- Replace the security type `Picker` with a simple toggle/switch. - Replace the security type `Picker` with a simple toggle/switch.
- Handle the “no keys yet” case gracefully by auto-opening the modal when user chooses key auth. - Handle the “no keys yet” case gracefully by auto-opening the modal when user
- Keep visual styling consistent with text inputs (button heights, paddings, colors). chooses key auth.
- Prepare the modal for importing private keys (paste text or select file) in a future phase. - Keep visual styling consistent with text inputs (button heights, paddings,
colors).
- Prepare the modal for importing private keys (paste text or select file) in a
future phase.
## Phase 1 — Selection UX + Styling ## Phase 1 — Selection UX + Styling
1) Replace security type `Picker` with toggle
1. Replace security type `Picker` with toggle
- Use a `SwitchField` (styled like inputs) labeled "Use Private Key". - Use a `SwitchField` (styled like inputs) labeled "Use Private Key".
- Value mapping: off → password; on → key. - Value mapping: off → password; on → key.
- When toggled ON and there is no selected key and no keys exist, auto-open Key Manager. - When toggled ON and there is no selected key and no keys exist, auto-open
Key Manager.
2) Remove inline key `Picker` 2. Remove inline key `Picker`
- Replace with a read-only field styled like inputs: label: "Private Key", value: current key label or "None". - Replace with a read-only field styled like inputs: label: "Private Key",
value: current key label or "None".
- The field is a `Pressable` that opens the Key Manager for selection. - The field is a `Pressable` that opens the Key Manager for selection.
- Disabled state (no keys): show "None" with hint "Open Key Manager to add a key". - Disabled state (no keys): show "None" with hint "Open Key Manager to add a
key".
3) Key Manager: add selection mode 3. Key Manager: add selection mode
- Add a radio-style control on each row to select the key for this session. - Add a radio-style control on each row to select the key for this session.
- Modal accepts an optional `selectedKeyId` and `onSelect(id)` callback. - Modal accepts an optional `selectedKeyId` and `onSelect(id)` callback.
- When user taps a row (or radio), call `onSelect(id)` and close the modal. - When user taps a row (or radio), call `onSelect(id)` and close the modal.
- Continue to support generate/rename/delete/set-default; selection should update live. - Continue to support generate/rename/delete/set-default; selection should
update live.
4) Styling parity 4. Styling parity
- Ensure the read-only "Private Key" field height/padding matches `TextField`. - Ensure the read-only "Private Key" field height/padding matches
`TextField`.
- Use consistent typography/colors for labels, values, and hints. - Use consistent typography/colors for labels, values, and hints.
5) Empty states 5. Empty states
- If no keys: show a friendly empty state in modal with primary action - If no keys: show a friendly empty state in modal with primary action
"Generate New Key" and secondary "Import Key" (Import lands in Phase 2). "Generate New Key" and secondary "Import Key" (Import lands in Phase 2).
Deliverables (Phase 1) Deliverables (Phase 1)
- Update `apps/mobile/src/app/index.tsx` form: - Update `apps/mobile/src/app/index.tsx` form:
- Toggle for auth type. - Toggle for auth type.
- Read-only key field that opens modal. - Read-only key field that opens modal.
@@ -50,53 +67,75 @@ Deliverables (Phase 1)
- Keep existing generate/rename/delete/set-default. - Keep existing generate/rename/delete/set-default.
## Phase 2 — Import Keys ## Phase 2 — Import Keys
1) Import entry points in modal
- Add "Import" button/menu in the modal header or as secondary action in empty state. 1. Import entry points in modal
- Add "Import" button/menu in the modal header or as secondary action in
empty state.
- Options: - Options:
- Paste PEM text (multiline input) - Paste PEM text (multiline input)
- Pick a file (use `expo-document-picker`) - Pick a file (use `expo-document-picker`)
2) Validation + Storage 2. Validation + Storage
- Validate PEM or OpenSSH formats; detect supported types (rsa/ecdsa/ed25519/ed448/dsa). - Validate PEM or OpenSSH formats; detect supported types
- Optional passphrase field (store encrypted if supported by library; otherwise prompt each use — confirm feasibility with SSH lib). (rsa/ecdsa/ed25519/ed448/dsa).
- On success, call the single `upsertPrivateKey({ keyId, value, metadata })` and close import flow. - Optional passphrase field (store encrypted if supported by library;
otherwise prompt each use — confirm feasibility with SSH lib).
- On success, call the single `upsertPrivateKey({ keyId, value, metadata })`
and close import flow.
3) UX details 3. UX details
- Show parse/validation errors inline. - Show parse/validation errors inline.
- Set initial `label` from filename (file import) or "Imported Key" (paste); allow editing label on success or selection. - Set initial `label` from filename (file import) or "Imported Key" (paste);
allow editing label on success or selection.
Deliverables (Phase 2) Deliverables (Phase 2)
- Modal import screen(s) with paste/file flows. - Modal import screen(s) with paste/file flows.
- PEM validation utility and error handling. - PEM validation utility and error handling.
## Phase 3 — Data Model + Semantics ## Phase 3 — Data Model + Semantics
1) Default key semantics
- Guarantee exactly one default key at most by flipping `isDefault` on upsert when `isDefault === true`.
- Consider: separate lightweight persistent "defaultId" in manifest root to avoid iterating all keys on default change.
2) Robustness 1. Default key semantics
- Add safety around manifest chunk growth: if `manifestChunkSize + newEntrySize > sizeLimit`, create a new chunk. - Guarantee exactly one default key at most by flipping `isDefault` on upsert
- Ensure `createdAtMs` is preserved across upserts (done) and add `modifiedAtMs` if useful. when `isDefault === true`.
- Consider: separate lightweight persistent "defaultId" in manifest root to
avoid iterating all keys on default change.
3) Logging + Dev ergonomics 2. Robustness
- Gate verbose logs behind a debug flag to avoid log spam in production builds. - Add safety around manifest chunk growth: if
`manifestChunkSize + newEntrySize > sizeLimit`, create a new chunk.
- Ensure `createdAtMs` is preserved across upserts (done) and add
`modifiedAtMs` if useful.
3. Logging + Dev ergonomics
- Gate verbose logs behind a debug flag to avoid log spam in production
builds.
## Phase 4 — Edge Cases & Polish ## Phase 4 — Edge Cases & Polish
- Deleting the currently-selected key: clear selection and show hint to pick/create a new key.
- Deleting the currently-selected key: clear selection and show hint to
pick/create a new key.
- Auto-select the default key when switching to key auth and no key is selected. - Auto-select the default key when switching to key auth and no key is selected.
- Handle failures from `SecureStore` (permissions/storage full) with user-friendly messages. - Handle failures from `SecureStore` (permissions/storage full) with
- Handle very large keys with chunking (already supported), surface an error if size exceeds safe thresholds. user-friendly messages.
- Accessibility: ensure labels/roles for controls in modal and the selection field. - Handle very large keys with chunking (already supported), surface an error if
size exceeds safe thresholds.
- Accessibility: ensure labels/roles for controls in modal and the selection
field.
## Phase 5 — Testing & QA ## Phase 5 — Testing & QA
- Unit test PEM parsing/validation (Phase 2). - Unit test PEM parsing/validation (Phase 2).
- E2E flows: - E2E flows:
- First run → toggle to key → auto-open modal → generate key → selected → connect. - First run → toggle to key → auto-open modal → generate key → selected →
connect.
- Import key by paste/file; rename; set default; delete. - Import key by paste/file; rename; set default; delete.
- Delete selected key; confirm the form state updates. - Delete selected key; confirm the form state updates.
## Implementation Notes ## Implementation Notes
- Keep a single write path for keys: `upsertPrivateKey` (invalidates the `keys` query).
- Prefer modal-based selection with a simple “field-as-button” in the form.
- For future passphrase support, confirm the SSH librarys API shape and storage expectations.
- Keep a single write path for keys: `upsertPrivateKey` (invalidates the `keys`
query).
- Prefer modal-based selection with a simple “field-as-button” in the form.
- For future passphrase support, confirm the SSH librarys API shape and storage
expectations.

View File

@@ -1,9 +1,30 @@
# Big TODOS
- real terminal emulation - real terminal emulation
- llm command suggestions - llm command suggestions
- llm output synthesis - llm output synthesis
- better keyboard management
- import private key
https://github.com/software-mansion/react-native-executorch ### Future reading
https://docs.expo.dev/guides/keyboard-handling/
https://kirillzyusko.github.io/react-native-keyboard-controller/
https://reactnativereusables.com/ #### AI Libraries
- https://github.com/software-mansion/react-native-executorch
- https://github.com/callstackincubator/ai
#### Keyboard handling
- https://docs.expo.dev/guides/keyboard-handling/
- https://kirillzyusko.github.io/react-native-keyboard-controller/
#### Styling and components
- https://reactnativereusables.com/ https://www.unistyl.es/
- https://github.com/Shopify/restyle
#### SSH Conn
- https://github.com/dylankenneally/react-native-ssh-sftp
- https://xtermjs.org/
- https://docs.expo.dev/versions/latest/sdk/webview/

386
pnpm-lock.yaml generated
View File

@@ -40,7 +40,7 @@ importers:
version: 1.5.20(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) version: 1.5.20(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
'@expo/vector-icons': '@expo/vector-icons':
specifier: ^15.0.2 specifier: ^15.0.2
version: 15.0.2(expo-font@14.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 15.0.2(expo-font@14.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
'@fressh/assets': '@fressh/assets':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/assets version: link:../../packages/assets
@@ -63,47 +63,47 @@ importers:
specifier: ^5.87.1 specifier: ^5.87.1
version: 5.87.1(react@19.1.0) version: 5.87.1(react@19.1.0)
expo: expo:
specifier: 54.0.1 specifier: 54.0.2
version: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-build-properties: expo-build-properties:
specifier: ~1.0.7 specifier: ~1.0.8
version: 1.0.7(expo@54.0.1) version: 1.0.8(expo@54.0.2)
expo-constants: expo-constants:
specifier: ~18.0.8 specifier: ~18.0.8
version: 18.0.8(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) version: 18.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
expo-font: expo-font:
specifier: ~14.0.7 specifier: ~14.0.8
version: 14.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 14.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-haptics: expo-haptics:
specifier: ~15.0.6 specifier: ~15.0.7
version: 15.0.6(expo@54.0.1) version: 15.0.7(expo@54.0.2)
expo-image: expo-image:
specifier: ~3.0.7 specifier: ~3.0.8
version: 3.0.7(expo@54.0.1)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 3.0.8(expo@54.0.2)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-linking: expo-linking:
specifier: ~8.0.7 specifier: ~8.0.8
version: 8.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 8.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-router: expo-router:
specifier: 6.0.0 specifier: 6.0.1
version: 6.0.0(@types/react@19.1.12)(expo-constants@18.0.8)(expo-linking@8.0.7)(expo@54.0.1)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 6.0.1(@types/react@19.1.12)(expo-constants@18.0.8)(expo-linking@8.0.8)(expo@54.0.2)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-secure-store: expo-secure-store:
specifier: ~15.0.6 specifier: ~15.0.7
version: 15.0.6(expo@54.0.1) version: 15.0.7(expo@54.0.2)
expo-splash-screen: expo-splash-screen:
specifier: ~31.0.8 specifier: ~31.0.9
version: 31.0.8(expo@54.0.1) version: 31.0.9(expo@54.0.2)
expo-status-bar: expo-status-bar:
specifier: ~3.0.7 specifier: ~3.0.8
version: 3.0.7(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) version: 3.0.8(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-symbols: expo-symbols:
specifier: ~1.0.6 specifier: ~1.0.7
version: 1.0.6(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) version: 1.0.7(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
expo-system-ui: expo-system-ui:
specifier: ~6.0.7 specifier: ~6.0.7
version: 6.0.7(expo@54.0.1)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) version: 6.0.7(expo@54.0.2)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
expo-web-browser: expo-web-browser:
specifier: ~15.0.6 specifier: ~15.0.7
version: 15.0.6(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) version: 15.0.7(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
react: react:
specifier: 19.1.0 specifier: 19.1.0
version: 19.1.0 version: 19.1.0
@@ -993,8 +993,8 @@ packages:
resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@expo/cli@54.0.1': '@expo/cli@54.0.2':
resolution: {integrity: sha512-vIITqb96FCHzCt4ctlkfghAcuNolNpYDE2cqBFt4zqgiesQW19xeucZRiw+IYF0XE0fo3/8OF29H+Aj3aisELA==} resolution: {integrity: sha512-WaMwVUC1twawsicKS/jabrc9b56EYLWDa1JvF1oopEE8H15+KLsgfjqN9IWtIR2wu5d6zhOT3ikTLc2cN4S4/Q==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
expo: '*' expo: '*'
@@ -1009,23 +1009,26 @@ packages:
'@expo/code-signing-certificates@0.0.5': '@expo/code-signing-certificates@0.0.5':
resolution: {integrity: sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==} resolution: {integrity: sha512-BNhXkY1bblxKZpltzAx98G2Egj9g1Q+JRcvR7E99DOj862FTCX+ZPsAUtPTr7aHxwtrL7+fL3r0JSmM9kBm+Bw==}
'@expo/config-plugins@11.0.7':
resolution: {integrity: sha512-kak5m27fPTzwmzYPbaYL6I67OFnhdrzV0h5JcoljrEC7uM3R18V/RrnEMzv10XQk+s+qmPfMkr0aK9YYgGqR6g==}
'@expo/config-plugins@54.0.0': '@expo/config-plugins@54.0.0':
resolution: {integrity: sha512-b2yFNKwaiHjDfh7K+zhMvRKA09gdaP/raqIzB112qeacVJaT66vka8m6cffYbbiXMe1srqofSJvyvr+g3H6+nA==} resolution: {integrity: sha512-b2yFNKwaiHjDfh7K+zhMvRKA09gdaP/raqIzB112qeacVJaT66vka8m6cffYbbiXMe1srqofSJvyvr+g3H6+nA==}
'@expo/config-plugins@54.0.1':
resolution: {integrity: sha512-NyBChhiWFL6VqSgU+LzK4R1vC397tEG2XFewVt4oMr4Pnalq/mJxBANQrR+dyV1RHhSyhy06RNiJIkQyngVWeg==}
'@expo/config-types@54.0.7': '@expo/config-types@54.0.7':
resolution: {integrity: sha512-f0UehgPd2gUqUtQ6euHAL6MqTT/A07r847Ztw2yZYWTUr0hRZr4nCP4U+lr8/pPtsHQYMKoPB1mOeAaTO25ruw==} resolution: {integrity: sha512-f0UehgPd2gUqUtQ6euHAL6MqTT/A07r847Ztw2yZYWTUr0hRZr4nCP4U+lr8/pPtsHQYMKoPB1mOeAaTO25ruw==}
'@expo/config-types@54.0.8':
resolution: {integrity: sha512-lyIn/x/Yz0SgHL7IGWtgTLg6TJWC9vL7489++0hzCHZ4iGjVcfZmPTUfiragZ3HycFFj899qN0jlhl49IHa94A==}
'@expo/config@12.0.8': '@expo/config@12.0.8':
resolution: {integrity: sha512-yFadXa5Cmja57EVOSyEYV1hF7kCaSbPnd1twx0MfvTr1Yj2abIbrEu2MUZqcvElNQOtgADnLRP0YJiuEdgoO5A==} resolution: {integrity: sha512-yFadXa5Cmja57EVOSyEYV1hF7kCaSbPnd1twx0MfvTr1Yj2abIbrEu2MUZqcvElNQOtgADnLRP0YJiuEdgoO5A==}
'@expo/devcert@1.2.0': '@expo/devcert@1.2.0':
resolution: {integrity: sha512-Uilcv3xGELD5t/b0eM4cxBFEKQRIivB3v7i+VhWLV/gL98aw810unLKKJbGAxAIhY6Ipyz8ChWibFsKFXYwstA==} resolution: {integrity: sha512-Uilcv3xGELD5t/b0eM4cxBFEKQRIivB3v7i+VhWLV/gL98aw810unLKKJbGAxAIhY6Ipyz8ChWibFsKFXYwstA==}
'@expo/devtools@0.1.6': '@expo/devtools@0.1.7':
resolution: {integrity: sha512-NE4TaCyUS/Cy2YxMdajt4grjfuJIsv6symUbQXk06Y96SyHy5DVzI1H0KdUca1HkPoZwwGCACFZZCN+Jvzuujw==} resolution: {integrity: sha512-dfIa9qMyXN+0RfU6SN4rKeXZyzKWsnz6xBSDccjL4IRiE+fQ0t84zg0yxgN4t/WK2JU5v6v4fby7W7Crv9gJvA==}
peerDependencies: peerDependencies:
react: '*' react: '*'
react-native: '*' react-native: '*'
@@ -1042,9 +1045,6 @@ packages:
resolution: {integrity: sha512-PrLA6fxScZfnLy7OHZ2GHXsDG9YbE7L5DbNhion6j/U/O+FQgz4VbxJarW5C00kMg1ll2u6EghB7ENAvL1T4qg==} resolution: {integrity: sha512-PrLA6fxScZfnLy7OHZ2GHXsDG9YbE7L5DbNhion6j/U/O+FQgz4VbxJarW5C00kMg1ll2u6EghB7ENAvL1T4qg==}
hasBin: true hasBin: true
'@expo/image-utils@0.8.6':
resolution: {integrity: sha512-8DMA76Sakq6dMLyJyoeWcxWpjcQUrr0V6jjsJ+Z3ZnEVKpWlxXO5SOc6mib1HOUDMtMqg4cwAhyKHZ98LmDj8Q==}
'@expo/image-utils@0.8.7': '@expo/image-utils@0.8.7':
resolution: {integrity: sha512-SXOww4Wq3RVXLyOaXiCCuQFguCDh8mmaHBv54h/R29wGl4jRY8GEyQEx8SypV/iHt1FbzsU/X3Qbcd9afm2W2w==} resolution: {integrity: sha512-SXOww4Wq3RVXLyOaXiCCuQFguCDh8mmaHBv54h/R29wGl4jRY8GEyQEx8SypV/iHt1FbzsU/X3Qbcd9afm2W2w==}
@@ -1054,8 +1054,8 @@ packages:
'@expo/json-file@10.0.7': '@expo/json-file@10.0.7':
resolution: {integrity: sha512-z2OTC0XNO6riZu98EjdNHC05l51ySeTto6GP7oSQrCvQgG9ARBwD1YvMQaVZ9wU7p/4LzSf1O7tckL3B45fPpw==} resolution: {integrity: sha512-z2OTC0XNO6riZu98EjdNHC05l51ySeTto6GP7oSQrCvQgG9ARBwD1YvMQaVZ9wU7p/4LzSf1O7tckL3B45fPpw==}
'@expo/metro-config@54.0.1': '@expo/metro-config@54.0.2':
resolution: {integrity: sha512-yq+aA38RjmTxFUUWK2xuKWIRAVfnIDf7ephSXcc5isNVGRylwnWE+8N2scWrOQt49ikePbvwWpakTsNfLVLZbw==} resolution: {integrity: sha512-RYnqFlqzTwdSOd/2OcLT/8td64ybfV5Q01YKZRUhUaszuOshS3hXVHdOPBpWDYQk6h2rWew8ds9FGiJaPyRVKw==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
peerDependenciesMeta: peerDependenciesMeta:
@@ -1076,8 +1076,8 @@ packages:
'@expo/metro@0.1.1': '@expo/metro@0.1.1':
resolution: {integrity: sha512-zvA9BE6myFoCxeiw/q3uE/kVkIwLTy27a+fDoEl7WQ7EvKfFeiXnRVhUplDMLGZIHH8VC38Gay6RBtVhnmOm5w==} resolution: {integrity: sha512-zvA9BE6myFoCxeiw/q3uE/kVkIwLTy27a+fDoEl7WQ7EvKfFeiXnRVhUplDMLGZIHH8VC38Gay6RBtVhnmOm5w==}
'@expo/osascript@2.3.6': '@expo/osascript@2.3.7':
resolution: {integrity: sha512-EMi4/YgWN/dHSkE9A4A5EJE8WrGnp71+BcVAKY80KMYtq5s6mOhLyDm8ytvHeUISUrPcPTRZ/6c8JMd6F8Ggmg==} resolution: {integrity: sha512-IClSOXxR0YUFxIriUJVqyYki7lLMIHrrzOaP01yxAL1G8pj2DWV5eW1y5jSzIcIfSCNhtGsshGd1tU/AYup5iQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
'@expo/package-manager@1.9.7': '@expo/package-manager@1.9.7':
@@ -1086,24 +1086,22 @@ packages:
'@expo/plist@0.4.6': '@expo/plist@0.4.6':
resolution: {integrity: sha512-6yklhtUWohs1rBSC8dGyBBpElEbosjXN0zJN/+1/B121n7pPWvd9y/UGJm+2x7b81VnW3AHmWVnbU/u0INQsqA==} resolution: {integrity: sha512-6yklhtUWohs1rBSC8dGyBBpElEbosjXN0zJN/+1/B121n7pPWvd9y/UGJm+2x7b81VnW3AHmWVnbU/u0INQsqA==}
'@expo/prebuild-config@10.0.8': '@expo/plist@0.4.7':
resolution: {integrity: sha512-9ibcRuWngmMnoYe25XXfZEWcPCdx6LiyzqHqpojsvHBI+sMsyZPf4b/5y/zmeJy3PKjR4LSzMRonEitTfUSL/A==} resolution: {integrity: sha512-dGxqHPvCZKeRKDU1sJZMmuyVtcASuSYh1LPFVaM1DuffqPL36n6FMEL0iUqq2Tx3xhWk8wCnWl34IKplUjJDdA==}
peerDependencies:
expo: '*'
'@expo/prebuild-config@54.0.1': '@expo/prebuild-config@54.0.1':
resolution: {integrity: sha512-f2FFDQlIh83rc89/AOdRBtO9nwkE4BCSji0oPBSq0YmAUSXZE7q/cYKABkArZNjuuwjb6jRjD8JfSMcwV7ybTA==} resolution: {integrity: sha512-f2FFDQlIh83rc89/AOdRBtO9nwkE4BCSji0oPBSq0YmAUSXZE7q/cYKABkArZNjuuwjb6jRjD8JfSMcwV7ybTA==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
'@expo/schema-utils@0.1.6': '@expo/schema-utils@0.1.7':
resolution: {integrity: sha512-arKkNlPZYD0uUxGPbSwV/6v4owmZcUNrXk9Vq/o+p2Eqt1DM9jIjbXwrqlmvkTsPkjUtNLDGB0EDiosVJ4OvDg==} resolution: {integrity: sha512-jWHoSuwRb5ZczjahrychMJ3GWZu54jK9ulNdh1d4OzAEq672K9E5yOlnlBsfIHWHGzUAT+0CL7Yt1INiXTz68g==}
'@expo/sdk-runtime-versions@1.0.0': '@expo/sdk-runtime-versions@1.0.0':
resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==}
'@expo/server@0.7.3': '@expo/server@0.7.4':
resolution: {integrity: sha512-lmlpJrjC0PLspPIF8ZzAalpbCxqbsqLRzIpolT3gZP48H6039BOlb1hAWTVgZvXmWkpvlYbt0PYG9o9GZxkQuw==} resolution: {integrity: sha512-8bfRzL7h1Qgrmf3auR71sPAcAuxnmNkRJs+8enL8vZi2+hihevLhrayDu7P0A/XGEq7wySAGvBBFfIB00Et/AA==}
engines: {node: '>=20.16.0'} engines: {node: '>=20.16.0'}
'@expo/spawn-async@1.7.2': '@expo/spawn-async@1.7.2':
@@ -1771,9 +1769,6 @@ packages:
'@react-native/normalize-colors@0.74.89': '@react-native/normalize-colors@0.74.89':
resolution: {integrity: sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==} resolution: {integrity: sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==}
'@react-native/normalize-colors@0.81.1':
resolution: {integrity: sha512-TsaeZlE8OYFy3PSWc+1VBmAzI2T3kInzqxmwXoGU4w1d4XFkQFg271Ja9GmDi9cqV3CnBfqoF9VPwRxVlc/l5g==}
'@react-native/normalize-colors@0.81.4': '@react-native/normalize-colors@0.81.4':
resolution: {integrity: sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg==} resolution: {integrity: sha512-9nRRHO1H+tcFqjb9gAM105Urtgcanbta2tuqCVY0NATHeFPDEAB7gPyiLxCHKMi1NbhP6TH0kxgSWXKZl1cyRg==}
@@ -3576,8 +3571,8 @@ packages:
react: '*' react: '*'
react-native: '*' react-native: '*'
expo-build-properties@1.0.7: expo-build-properties@1.0.8:
resolution: {integrity: sha512-HbsLEjhmVaGudNpO3qYSdZddK+B82D0ff5r9hZ1huOGqwPnb21TmajsSjHC0q7M+9MJ2P+IunC4qYib21IWSAg==} resolution: {integrity: sha512-Ram9Jcg2WkbE7bRopWacMnXIhqUP6P13cQoj4rSfXLpJVOyGGvuMiw3rs1vzMbWSHxhvvsX2NvYXXIrZatexuw==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
@@ -3587,26 +3582,26 @@ packages:
expo: '*' expo: '*'
react-native: '*' react-native: '*'
expo-file-system@19.0.11: expo-file-system@19.0.12:
resolution: {integrity: sha512-xrtd4YobW5pKYJt4tGNbmzjb9ojg5J4H1iavUs2MDt4S/jX+IObLuJ8tWxBI7Zce5+0i9j4nU/V0gz/18AMJ8w==} resolution: {integrity: sha512-gqpxpnjfhzXLcqMOi49isB5S1Af49P9410fsaFfnLZWN3X6Dwc8EplDwbaolOI/wnGwP81P+/nDn5RNmU6m7mQ==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
react-native: '*' react-native: '*'
expo-font@14.0.7: expo-font@14.0.8:
resolution: {integrity: sha512-jW6aEHUwn+SKizE1iJsaC0nLFb7ipy2wqUrceg+k7KyVZW74DRyDVjuJYzyuJE8BJutulsiYBzvx6K0axboULA==} resolution: {integrity: sha512-bTUHaJWRZ7ywP8dg3f+wfOwv6RwMV3mWT2CDUIhsK70GjNGlCtiWOCoHsA5Od/esPaVxqc37cCBvQGQRFStRlA==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
react: '*' react: '*'
react-native: '*' react-native: '*'
expo-haptics@15.0.6: expo-haptics@15.0.7:
resolution: {integrity: sha512-Unns//atxLq4UkDrgvwNpzOB2pm6hPS1nhZVL8BIfOebU/++JQlKJYPam4sRn7w/vMbRYpJ8BmC96mRs3QR0Tg==} resolution: {integrity: sha512-7flWsYPrwjJxZ8x82RiJtzsnk1Xp9ahnbd9PhCy3NnsemyMApoWIEUr4waPqFr80DtiLZfhD9VMLL1CKa8AImQ==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
expo-image@3.0.7: expo-image@3.0.8:
resolution: {integrity: sha512-JlokOLUnIx9fKWgC6jlP/0C1TJd6ESNo/hKVO49a989YvHodslj1GGRy9+CR2ak4WBJiBv1X6SrYMchMXobfSg==} resolution: {integrity: sha512-L83fTHVjvE5hACxUXPk3dpABteI/IypeqxKMeOAAcT2eB/jbqT53ddsYKEvKAP86eoByQ7+TCtw9AOUizEtaTQ==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
react: '*' react: '*'
@@ -3616,14 +3611,14 @@ packages:
react-native-web: react-native-web:
optional: true optional: true
expo-keep-awake@15.0.6: expo-keep-awake@15.0.7:
resolution: {integrity: sha512-xO/KbQQjSdhHR87x2uZsMnIDXsDO9apa64jqxG5t0WKSBujdS3uDmvuYF7EwiJc8fKEWZ500Co51nOtO0wU1dg==} resolution: {integrity: sha512-CgBNcWVPnrIVII5G54QDqoE125l+zmqR4HR8q+MQaCfHet+dYpS5vX5zii/RMayzGN4jPgA4XYIQ28ePKFjHoA==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
react: '*' react: '*'
expo-linking@8.0.7: expo-linking@8.0.8:
resolution: {integrity: sha512-DCPEDg522ryJp752kcajGR8kWgQfKMc/YbjzApb4Uzm2kJNW+0JXrIwsLPTl/mn9XxcOkGrE81B1L5EwQZKHkw==} resolution: {integrity: sha512-MyeMcbFDKhXh4sDD1EHwd0uxFQNAc6VCrwBkNvvvufUsTYFq3glTA9Y8a+x78CPpjNqwNAamu74yIaIz7IEJyg==}
peerDependencies: peerDependencies:
react: '*' react: '*'
react-native: '*' react-native: '*'
@@ -3638,14 +3633,14 @@ packages:
react: '*' react: '*'
react-native: '*' react-native: '*'
expo-router@6.0.0: expo-router@6.0.1:
resolution: {integrity: sha512-4/SI/4f4wOmbxyQ3TH2nvRdbmlggQx2/WO5mWSuSsazUUerjc8h3lo9NkKyEZRrkeq09jYcfQQhbbl8yCnW/Gg==} resolution: {integrity: sha512-5wXkWyNMqUbjCWH0PRkOM0P6UsgLVdgchDkiLz5FY7HfU00ToBcxij965bqtlaATBgoaIo4DuLu6EgxewrKJ8Q==}
peerDependencies: peerDependencies:
'@react-navigation/drawer': ^7.5.0 '@react-navigation/drawer': ^7.5.0
'@testing-library/react-native': '>= 12.0.0' '@testing-library/react-native': '>= 12.0.0'
expo: '*' expo: '*'
expo-constants: ^18.0.7 expo-constants: ^18.0.8
expo-linking: ^8.0.7 expo-linking: ^8.0.8
react: '*' react: '*'
react-dom: '*' react-dom: '*'
react-native: '*' react-native: '*'
@@ -3671,24 +3666,24 @@ packages:
react-server-dom-webpack: react-server-dom-webpack:
optional: true optional: true
expo-secure-store@15.0.6: expo-secure-store@15.0.7:
resolution: {integrity: sha512-jzCsjnoMJYuRFxb4oHlwQY71WFIiOFiqQlvtay3Ct6OoKugR3/qjBwkcBoDsr1LsbFBVEkOMEU2i59peBMG1Jg==} resolution: {integrity: sha512-9q7+G1Zxr5P6J5NRIlm86KulvmYwc6UnQlYPjQLDu1drDnerz6AT6l884dPu29HgtDTn4rR0heYeeGFhMKM7/Q==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
expo-splash-screen@31.0.8: expo-splash-screen@31.0.9:
resolution: {integrity: sha512-NNgNhrqkuJAt98k9CKJ9JeCInv5g/xRhe1fWbciQrqTQWPXeIGg3tn5T5HNFwfRD1aKr2uOs1Ctly8rE6i5vtQ==} resolution: {integrity: sha512-pYLWFrEHn/c8e1a8oDzrlcyYI08oG4i37KA+VN5wwoC1DsQ0rm3kyAk0bmoydV1mBzNfXQFYbA1BGftXJl1f0w==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
expo-status-bar@3.0.7: expo-status-bar@3.0.8:
resolution: {integrity: sha512-8AyNDAsOIR0sAh8uUuOnCNrntr2sM2Q8MW173bQJQIDRNClK7WLVnho1mQhZU3it7MrSWp3QOyUTNU0RcQrL1w==} resolution: {integrity: sha512-L248XKPhum7tvREoS1VfE0H6dPCaGtoUWzRsUv7hGKdiB4cus33Rc0sxkWkoQ77wE8stlnUlL5lvmT0oqZ3ZBw==}
peerDependencies: peerDependencies:
react: '*' react: '*'
react-native: '*' react-native: '*'
expo-symbols@1.0.6: expo-symbols@1.0.7:
resolution: {integrity: sha512-866OfsPJUjT9OhxP6ol/tnQLUY3h2LGHXGpb2SR9OLbM2M/xv+5Ko1+GBMcTbJ3wqW6uZKEgQBNLLKXUIvi/bg==} resolution: {integrity: sha512-ZqFUeTXbwO6BrE00n37wTXYfJmsjFrfB446jeB9k9w7aA8a6eugNUIzNsUIUfbFWoOiY4wrGmpLSLPBwk4PH+g==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
react-native: '*' react-native: '*'
@@ -3703,14 +3698,14 @@ packages:
react-native-web: react-native-web:
optional: true optional: true
expo-web-browser@15.0.6: expo-web-browser@15.0.7:
resolution: {integrity: sha512-f3LPRwYGHQ6lM2PZX1Fz79mfa90e3s8Zs9UKv2TqSqsM85YmqTiT5pgd5tLHh55HgWjSMeISCCtahe6WKeWgCw==} resolution: {integrity: sha512-eXnfO3FQ2WthTA8uEPNJ7SDRfPaLIU/P2k082HGEYIHAFZMwh2o9Wo+SDVytO3E95TAv1qwhggUjOrczYzxteQ==}
peerDependencies: peerDependencies:
expo: '*' expo: '*'
react-native: '*' react-native: '*'
expo@54.0.1: expo@54.0.2:
resolution: {integrity: sha512-BJSraR0CM8aUtrSlk8fgOXHhhsB5SeFqc+rJPsnMpdguN60PNkoGF+/ulr9dYfLaPa2w51tW5PmeVTLzESdXbA==} resolution: {integrity: sha512-0YcsvMuiZlVFVZRdD4PlhwsYrTmcN1qm5b1IRSLIeLZjH6ZnQwBb3KBSnK8WRC9V6EUPSbuLGpNT5AEewrtakQ==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
'@expo/dom-webview': '*' '@expo/dom-webview': '*'
@@ -7756,24 +7751,24 @@ snapshots:
'@eslint/core': 0.15.2 '@eslint/core': 0.15.2
levn: 0.4.1 levn: 0.4.1
'@expo/cli@54.0.1(expo-router@6.0.0)(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))': '@expo/cli@54.0.2(expo-router@6.0.1)(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))':
dependencies: dependencies:
'@0no-co/graphql.web': 1.2.0 '@0no-co/graphql.web': 1.2.0
'@expo/code-signing-certificates': 0.0.5 '@expo/code-signing-certificates': 0.0.5
'@expo/config': 12.0.8 '@expo/config': 12.0.8
'@expo/config-plugins': 54.0.0 '@expo/config-plugins': 54.0.1
'@expo/devcert': 1.2.0 '@expo/devcert': 1.2.0
'@expo/env': 2.0.7 '@expo/env': 2.0.7
'@expo/image-utils': 0.8.7 '@expo/image-utils': 0.8.7
'@expo/json-file': 10.0.7 '@expo/json-file': 10.0.7
'@expo/metro': 0.1.1 '@expo/metro': 0.1.1
'@expo/metro-config': 54.0.1(expo@54.0.1) '@expo/metro-config': 54.0.2(expo@54.0.2)
'@expo/osascript': 2.3.6 '@expo/osascript': 2.3.7
'@expo/package-manager': 1.9.7 '@expo/package-manager': 1.9.7
'@expo/plist': 0.4.6 '@expo/plist': 0.4.7
'@expo/prebuild-config': 54.0.1(expo@54.0.1) '@expo/prebuild-config': 54.0.1(expo@54.0.2)
'@expo/schema-utils': 0.1.6 '@expo/schema-utils': 0.1.7
'@expo/server': 0.7.3 '@expo/server': 0.7.4
'@expo/spawn-async': 1.7.2 '@expo/spawn-async': 1.7.2
'@expo/ws-tunnel': 1.0.6 '@expo/ws-tunnel': 1.0.6
'@expo/xcpretty': 4.3.2 '@expo/xcpretty': 4.3.2
@@ -7791,7 +7786,7 @@ snapshots:
connect: 3.7.0 connect: 3.7.0
debug: 4.4.1 debug: 4.4.1
env-editor: 0.4.2 env-editor: 0.4.2
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
freeport-async: 2.0.0 freeport-async: 2.0.0
getenv: 2.0.0 getenv: 2.0.0
glob: 10.4.5 glob: 10.4.5
@@ -7823,7 +7818,7 @@ snapshots:
wrap-ansi: 7.0.0 wrap-ansi: 7.0.0
ws: 8.18.3 ws: 8.18.3
optionalDependencies: optionalDependencies:
expo-router: 6.0.0(@types/react@19.1.12)(expo-constants@18.0.8)(expo-linking@8.0.7)(expo@54.0.1)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo-router: 6.0.1(@types/react@19.1.12)(expo-constants@18.0.8)(expo-linking@8.0.8)(expo@54.0.2)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
@@ -7836,25 +7831,6 @@ snapshots:
node-forge: 1.3.1 node-forge: 1.3.1
nullthrows: 1.1.1 nullthrows: 1.1.1
'@expo/config-plugins@11.0.7':
dependencies:
'@expo/config-types': 54.0.7
'@expo/json-file': 10.0.7
'@expo/plist': 0.4.6
'@expo/sdk-runtime-versions': 1.0.0
chalk: 4.1.2
debug: 4.4.1
getenv: 2.0.0
glob: 10.4.5
resolve-from: 5.0.0
semver: 7.7.2
slash: 3.0.0
slugify: 1.6.6
xcode: 3.0.1
xml2js: 0.6.0
transitivePeerDependencies:
- supports-color
'@expo/config-plugins@54.0.0': '@expo/config-plugins@54.0.0':
dependencies: dependencies:
'@expo/config-types': 54.0.7 '@expo/config-types': 54.0.7
@@ -7874,8 +7850,29 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@expo/config-plugins@54.0.1':
dependencies:
'@expo/config-types': 54.0.8
'@expo/json-file': 10.0.7
'@expo/plist': 0.4.7
'@expo/sdk-runtime-versions': 1.0.0
chalk: 4.1.2
debug: 4.4.1
getenv: 2.0.0
glob: 10.4.5
resolve-from: 5.0.0
semver: 7.7.2
slash: 3.0.0
slugify: 1.6.6
xcode: 3.0.1
xml2js: 0.6.0
transitivePeerDependencies:
- supports-color
'@expo/config-types@54.0.7': {} '@expo/config-types@54.0.7': {}
'@expo/config-types@54.0.8': {}
'@expo/config@12.0.8': '@expo/config@12.0.8':
dependencies: dependencies:
'@babel/code-frame': 7.10.4 '@babel/code-frame': 7.10.4
@@ -7902,7 +7899,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@expo/devtools@0.1.6(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': '@expo/devtools@0.1.7(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)':
dependencies: dependencies:
chalk: 4.1.2 chalk: 4.1.2
optionalDependencies: optionalDependencies:
@@ -7935,19 +7932,6 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@expo/image-utils@0.8.6':
dependencies:
'@expo/spawn-async': 1.7.2
chalk: 4.1.2
getenv: 2.0.0
jimp-compact: 0.16.1
parse-png: 2.1.0
resolve-from: 5.0.0
resolve-global: 1.0.0
semver: 7.7.2
temp-dir: 2.0.0
unique-string: 2.0.0
'@expo/image-utils@0.8.7': '@expo/image-utils@0.8.7':
dependencies: dependencies:
'@expo/spawn-async': 1.7.2 '@expo/spawn-async': 1.7.2
@@ -7971,7 +7955,7 @@ snapshots:
'@babel/code-frame': 7.10.4 '@babel/code-frame': 7.10.4
json5: 2.2.3 json5: 2.2.3
'@expo/metro-config@54.0.1(expo@54.0.1)': '@expo/metro-config@54.0.2(expo@54.0.2)':
dependencies: dependencies:
'@babel/code-frame': 7.27.1 '@babel/code-frame': 7.27.1
'@babel/core': 7.28.3 '@babel/core': 7.28.3
@@ -7995,16 +7979,16 @@ snapshots:
postcss: 8.4.49 postcss: 8.4.49
resolve-from: 5.0.0 resolve-from: 5.0.0
optionalDependencies: optionalDependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- supports-color - supports-color
- utf-8-validate - utf-8-validate
'@expo/metro-runtime@6.1.1(expo@54.0.1)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': '@expo/metro-runtime@6.1.1(expo@54.0.2)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)':
dependencies: dependencies:
anser: 1.4.10 anser: 1.4.10
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
pretty-format: 29.7.0 pretty-format: 29.7.0
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
@@ -8032,7 +8016,7 @@ snapshots:
- supports-color - supports-color
- utf-8-validate - utf-8-validate
'@expo/osascript@2.3.6': '@expo/osascript@2.3.7':
dependencies: dependencies:
'@expo/spawn-async': 1.7.2 '@expo/spawn-async': 1.7.2
exec-async: 2.2.0 exec-async: 2.2.0
@@ -8052,43 +8036,33 @@ snapshots:
base64-js: 1.5.1 base64-js: 1.5.1
xmlbuilder: 15.1.1 xmlbuilder: 15.1.1
'@expo/prebuild-config@10.0.8(expo@54.0.1)': '@expo/plist@0.4.7':
dependencies: dependencies:
'@expo/config': 12.0.8 '@xmldom/xmldom': 0.8.11
'@expo/config-plugins': 11.0.7 base64-js: 1.5.1
'@expo/config-types': 54.0.7 xmlbuilder: 15.1.1
'@expo/image-utils': 0.8.6
'@expo/json-file': 10.0.6
'@react-native/normalize-colors': 0.81.1
debug: 4.4.1
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
resolve-from: 5.0.0
semver: 7.7.2
xml2js: 0.6.0
transitivePeerDependencies:
- supports-color
'@expo/prebuild-config@54.0.1(expo@54.0.1)': '@expo/prebuild-config@54.0.1(expo@54.0.2)':
dependencies: dependencies:
'@expo/config': 12.0.8 '@expo/config': 12.0.8
'@expo/config-plugins': 54.0.0 '@expo/config-plugins': 54.0.1
'@expo/config-types': 54.0.7 '@expo/config-types': 54.0.8
'@expo/image-utils': 0.8.7 '@expo/image-utils': 0.8.7
'@expo/json-file': 10.0.7 '@expo/json-file': 10.0.7
'@react-native/normalize-colors': 0.81.4 '@react-native/normalize-colors': 0.81.4
debug: 4.4.1 debug: 4.4.1
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
resolve-from: 5.0.0 resolve-from: 5.0.0
semver: 7.7.2 semver: 7.7.2
xml2js: 0.6.0 xml2js: 0.6.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@expo/schema-utils@0.1.6': {} '@expo/schema-utils@0.1.7': {}
'@expo/sdk-runtime-versions@1.0.0': {} '@expo/sdk-runtime-versions@1.0.0': {}
'@expo/server@0.7.3': '@expo/server@0.7.4':
dependencies: dependencies:
abort-controller: 3.0.0 abort-controller: 3.0.0
debug: 4.4.1 debug: 4.4.1
@@ -8101,9 +8075,9 @@ snapshots:
'@expo/sudo-prompt@9.3.2': {} '@expo/sudo-prompt@9.3.2': {}
'@expo/vector-icons@15.0.2(expo-font@14.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': '@expo/vector-icons@15.0.2(expo-font@14.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)':
dependencies: dependencies:
expo-font: 14.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo-font: 14.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
@@ -8770,8 +8744,6 @@ snapshots:
'@react-native/normalize-colors@0.74.89': {} '@react-native/normalize-colors@0.74.89': {}
'@react-native/normalize-colors@0.81.1': {}
'@react-native/normalize-colors@0.81.4': {} '@react-native/normalize-colors@0.81.4': {}
'@react-native/virtualized-lists@0.81.4(@types/react@19.1.12)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': '@react-native/virtualized-lists@0.81.4(@types/react@19.1.12)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)':
@@ -9827,7 +9799,7 @@ snapshots:
'@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3) '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3)
'@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.3) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.3)
babel-preset-expo@54.0.0(@babel/core@7.28.3)(@babel/runtime@7.28.3)(expo@54.0.1)(react-refresh@0.14.2): babel-preset-expo@54.0.0(@babel/core@7.28.3)(@babel/runtime@7.28.3)(expo@54.0.2)(react-refresh@0.14.2):
dependencies: dependencies:
'@babel/helper-module-imports': 7.27.1 '@babel/helper-module-imports': 7.27.1
'@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3) '@babel/plugin-proposal-decorators': 7.28.0(@babel/core@7.28.3)
@@ -9854,7 +9826,7 @@ snapshots:
resolve-from: 5.0.0 resolve-from: 5.0.0
optionalDependencies: optionalDependencies:
'@babel/runtime': 7.28.3 '@babel/runtime': 7.28.3
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- supports-color - supports-color
@@ -10902,63 +10874,63 @@ snapshots:
signal-exit: 3.0.7 signal-exit: 3.0.7
strip-final-newline: 2.0.0 strip-final-newline: 2.0.0
expo-asset@12.0.8(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-asset@12.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@expo/image-utils': 0.8.7 '@expo/image-utils': 0.8.7
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-constants: 18.0.8(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) expo-constants: 18.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
expo-build-properties@1.0.7(expo@54.0.1): expo-build-properties@1.0.8(expo@54.0.2):
dependencies: dependencies:
ajv: 8.17.1 ajv: 8.17.1
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
semver: 7.7.2 semver: 7.7.2
expo-constants@18.0.8(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)): expo-constants@18.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)):
dependencies: dependencies:
'@expo/config': 12.0.8 '@expo/config': 12.0.8
'@expo/env': 2.0.7 '@expo/env': 2.0.7
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
expo-file-system@19.0.11(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)): expo-file-system@19.0.12(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)):
dependencies: dependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
expo-font@14.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-font@14.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
fontfaceobserver: 2.3.0 fontfaceobserver: 2.3.0
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
expo-haptics@15.0.6(expo@54.0.1): expo-haptics@15.0.7(expo@54.0.2):
dependencies: dependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-image@3.0.7(expo@54.0.1)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-image@3.0.8(expo@54.0.2)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
optionalDependencies: optionalDependencies:
react-native-web: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-native-web: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
expo-keep-awake@15.0.6(expo@54.0.1)(react@19.1.0): expo-keep-awake@15.0.7(expo@54.0.2)(react@19.1.0):
dependencies: dependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react: 19.1.0 react: 19.1.0
expo-linking@8.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-linking@8.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
expo-constants: 18.0.8(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) expo-constants: 18.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
invariant: 2.2.4 invariant: 2.2.4
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
@@ -10981,11 +10953,11 @@ snapshots:
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
expo-router@6.0.0(@types/react@19.1.12)(expo-constants@18.0.8)(expo-linking@8.0.7)(expo@54.0.1)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-router@6.0.1(@types/react@19.1.12)(expo-constants@18.0.8)(expo-linking@8.0.8)(expo@54.0.2)(react-dom@19.1.0(react@19.1.0))(react-native-gesture-handler@2.28.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-reanimated@4.1.0(@babel/core@7.28.3)(react-native-worklets@0.5.1(@babel/core@7.28.3)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@expo/metro-runtime': 6.1.1(expo@54.0.1)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@expo/metro-runtime': 6.1.1(expo@54.0.2)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
'@expo/schema-utils': 0.1.6 '@expo/schema-utils': 0.1.7
'@expo/server': 0.7.3 '@expo/server': 0.7.4
'@radix-ui/react-slot': 1.2.0(@types/react@19.1.12)(react@19.1.0) '@radix-ui/react-slot': 1.2.0(@types/react@19.1.12)(react@19.1.0)
'@radix-ui/react-tabs': 1.1.13(@types/react@19.1.12)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-tabs': 1.1.13(@types/react@19.1.12)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
'@react-navigation/bottom-tabs': 7.4.7(@react-navigation/native@7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@react-navigation/bottom-tabs': 7.4.7(@react-navigation/native@7.1.17(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.6.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
@@ -10994,9 +10966,9 @@ snapshots:
client-only: 0.0.1 client-only: 0.0.1
debug: 4.4.1 debug: 4.4.1
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-constants: 18.0.8(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) expo-constants: 18.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
expo-linking: 8.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo-linking: 8.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
fast-deep-equal: 3.1.3 fast-deep-equal: 3.1.3
invariant: 2.2.4 invariant: 2.2.4
nanoid: 3.3.11 nanoid: 3.3.11
@@ -11024,63 +10996,63 @@ snapshots:
- '@types/react-dom' - '@types/react-dom'
- supports-color - supports-color
expo-secure-store@15.0.6(expo@54.0.1): expo-secure-store@15.0.7(expo@54.0.2):
dependencies: dependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-splash-screen@31.0.8(expo@54.0.1): expo-splash-screen@31.0.9(expo@54.0.2):
dependencies: dependencies:
'@expo/prebuild-config': 10.0.8(expo@54.0.1) '@expo/prebuild-config': 54.0.1(expo@54.0.2)
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
expo-status-bar@3.0.7(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo-status-bar@3.0.8(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
react: 19.1.0 react: 19.1.0
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) react-native-is-edge-to-edge: 1.2.1(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-symbols@1.0.6(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)): expo-symbols@1.0.7(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)):
dependencies: dependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
sf-symbols-typescript: 2.1.0 sf-symbols-typescript: 2.1.0
expo-system-ui@6.0.7(expo@54.0.1)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)): expo-system-ui@6.0.7(expo@54.0.2)(react-native-web@0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)):
dependencies: dependencies:
'@react-native/normalize-colors': 0.81.4 '@react-native/normalize-colors': 0.81.4
debug: 4.4.1 debug: 4.4.1
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
optionalDependencies: optionalDependencies:
react-native-web: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) react-native-web: 0.21.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
expo-web-browser@15.0.6(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)): expo-web-browser@15.0.7(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)):
dependencies: dependencies:
expo: 54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo: 54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0) react-native: 0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)
expo@54.0.1(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.0)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): expo@54.0.2(@babel/core@7.28.3)(@expo/metro-runtime@6.1.1)(expo-router@6.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0):
dependencies: dependencies:
'@babel/runtime': 7.28.3 '@babel/runtime': 7.28.3
'@expo/cli': 54.0.1(expo-router@6.0.0)(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) '@expo/cli': 54.0.2(expo-router@6.0.1)(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
'@expo/config': 12.0.8 '@expo/config': 12.0.8
'@expo/config-plugins': 54.0.0 '@expo/config-plugins': 54.0.1
'@expo/devtools': 0.1.6(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@expo/devtools': 0.1.7(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
'@expo/fingerprint': 0.15.0 '@expo/fingerprint': 0.15.0
'@expo/metro': 0.1.1 '@expo/metro': 0.1.1
'@expo/metro-config': 54.0.1(expo@54.0.1) '@expo/metro-config': 54.0.2(expo@54.0.2)
'@expo/vector-icons': 15.0.2(expo-font@14.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@expo/vector-icons': 15.0.2(expo-font@14.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
'@ungap/structured-clone': 1.3.0 '@ungap/structured-clone': 1.3.0
babel-preset-expo: 54.0.0(@babel/core@7.28.3)(@babel/runtime@7.28.3)(expo@54.0.1)(react-refresh@0.14.2) babel-preset-expo: 54.0.0(@babel/core@7.28.3)(@babel/runtime@7.28.3)(expo@54.0.2)(react-refresh@0.14.2)
expo-asset: 12.0.8(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo-asset: 12.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-constants: 18.0.8(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) expo-constants: 18.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
expo-file-system: 19.0.11(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0)) expo-file-system: 19.0.12(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))
expo-font: 14.0.7(expo@54.0.1)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo-font: 14.0.8(expo@54.0.2)(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
expo-keep-awake: 15.0.6(expo@54.0.1)(react@19.1.0) expo-keep-awake: 15.0.7(expo@54.0.2)(react@19.1.0)
expo-modules-autolinking: 3.0.10 expo-modules-autolinking: 3.0.10
expo-modules-core: 3.0.15(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) expo-modules-core: 3.0.15(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
pretty-format: 29.7.0 pretty-format: 29.7.0
@@ -11089,7 +11061,7 @@ snapshots:
react-refresh: 0.14.2 react-refresh: 0.14.2
whatwg-url-without-unicode: 8.0.0-3 whatwg-url-without-unicode: 8.0.0-3
optionalDependencies: optionalDependencies:
'@expo/metro-runtime': 6.1.1(expo@54.0.1)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) '@expo/metro-runtime': 6.1.1(expo@54.0.2)(react-dom@19.1.0(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.3)(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- bufferutil - bufferutil