diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js index 0317d34f..bc370b16 100644 --- a/apps/mobile/metro.config.js +++ b/apps/mobile/metro.config.js @@ -10,5 +10,15 @@ config.resolver.nodeModulesPaths = [ path.resolve(projectRoot, 'node_modules'), path.resolve(workspaceRoot, 'node_modules'), ] +config.resolver.resolveRequest = (context, moduleName, platform) => { + if (moduleName === 'isomorphic-git') { + return { + filePath: path.resolve(workspaceRoot, 'node_modules/isomorphic-git/index.js'), + type: 'sourceFile', + } + } + + return context.resolveRequest(context, moduleName, platform) +} module.exports = config diff --git a/apps/mobile/package.json b/apps/mobile/package.json index bd97fcb8..91166bd2 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -15,14 +15,16 @@ "dependencies": { "@10play/tentap-editor": "^1.0.1", "@tolaria/markdown": "workspace:*", - "expo": "~55.0.20", + "buffer": "^6.0.3", + "expo": "~55.0.23", "expo-auth-session": "~55.0.15", "expo-dev-client": "~55.0.32", - "expo-file-system": "55.0.17", - "expo-modules-core": "55.0.24", + "expo-file-system": "55.0.19", + "expo-modules-core": "55.0.25", "expo-secure-store": "~55.0.13", - "expo-status-bar": "~55.0.5", - "expo-web-browser": "~55.0.14", + "expo-status-bar": "~55.0.6", + "expo-web-browser": "~55.0.15", + "isomorphic-git": "^1.37.6", "phosphor-react-native": "^3.0.6", "react": "19.2.0", "react-native": "0.83.6", diff --git a/apps/mobile/src/MobileApp.tsx b/apps/mobile/src/MobileApp.tsx index 065f70e2..5aca22e8 100644 --- a/apps/mobile/src/MobileApp.tsx +++ b/apps/mobile/src/MobileApp.tsx @@ -74,8 +74,7 @@ import { useMobileVaultRuntimeLoader } from './useMobileVaultRuntimeLoader' import type { MobileNotePropertyPatch } from './mobileNoteProperties' import { useMobileGitSyncFlow } from './useMobileGitSyncFlow' import { useMobileAiSettingsFlow } from './useMobileAiSettingsFlow' -import { createNativeMobileGitTransport } from './mobileNativeGitTransport' -import { loadExpoMobileGitNativeModule } from './mobileExpoNativeGitModule' +import { createNativeIsomorphicMobileGitTransport } from './mobileNativeIsomorphicGitTransport' import { applyMobileRawNoteContent } from './mobileRawNoteProjection' import { createMobileSidebarSections, @@ -96,7 +95,7 @@ export function MobileApp() { const aiSettingsStorage = useMemo(() => createNativeMobileAiSettingsStorage(), []) const appStateStorage = useMemo(() => createNativeMobileAppStateStorage(), []) const gitCredentialStorage = useMemo(() => createNativeMobileGitCredentialStorage(), []) - const gitTransport = useMemo(() => createNativeMobileGitTransport(loadExpoMobileGitNativeModule()), []) + const gitTransport = useMemo(() => createNativeIsomorphicMobileGitTransport(gitCredentialStorage), [gitCredentialStorage]) const gitHubOAuthClientIdState = useMemo(() => currentMobileGitHubOAuthClientIdState(), []) const vaultMetadataStorage = useMemo(() => createNativeMobileVaultMetadataStorage(), []) const [activeVaultMetadata, setActiveVaultMetadata] = useState(defaultMobileVaultMetadata) @@ -118,12 +117,22 @@ export function MobileApp() { ) const selectedEditorMode = editorModeByNoteId[selectedNote.id] ?? 'rich' const selectedSaveState = saveStateByNoteId[selectedNote.id] ?? idleMobileEditorSaveState + const reloadSyncedVault = useCallback(() => { + void loadDemoVaultNotes(activeVaultMetadata) + .then((notes) => { + setAvailableNotes(notes) + setCompactNavigation((state) => selectLoadedNote(state, notes, state.selectedNoteId)) + }) + .catch(() => {}) + }, [activeVaultMetadata]) const gitSyncFlow = useMobileGitSyncFlow({ createGitHubOAuthSession: createNativeMobileGitHubOAuthSessionFromEnvironment, credentialStorage: gitCredentialStorage, gitTransport, + onSynced: reloadSyncedVault, vault: activeVaultMetadata, }) + const markGitLocalChanges = gitSyncFlow.markLocalChanges const aiSettingsFlow = useMobileAiSettingsFlow({ secretStorage: aiProviderSecretStorage, settingsStorage: aiSettingsStorage, @@ -137,10 +146,11 @@ export function MobileApp() { setSaveStateByNoteId((state) => ({ ...state, [noteId]: saveState })) }, onSavedDraft: (draft) => { + markGitLocalChanges() setAvailableNotes((notes) => applySavedMobileEditorDraft({ draft, notes })) }, }), - [activeVaultMetadata], + [activeVaultMetadata, markGitLocalChanges], ) const applyLoadedVaultRuntime = useCallback(({ activeVault, notes, selectedNoteId }: MobileVaultRuntime) => { @@ -196,13 +206,19 @@ export function MobileApp() { ? { label: 'Saved', state: 'saved' } : { label: 'Save failed', state: 'failed' }, })) + if (result.status === 'saved') { + markGitLocalChanges() + } }) .catch(() => { setSaveStateByNoteId((state) => ({ ...state, [noteId]: { label: 'Save failed', state: 'failed' } })) }) - }, [activeVaultMetadata, selectedNote.id]) + }, [activeVaultMetadata, markGitLocalChanges, selectedNote.id]) const deleteFlow = useMobileNoteDeleteFlow({ - deleteNote: (noteId) => deleteDemoVaultNote(noteId, activeVaultMetadata), + deleteNote: async (noteId) => { + await deleteDemoVaultNote(noteId, activeVaultMetadata) + markGitLocalChanges() + }, loadNotes: () => loadDemoVaultNotes(activeVaultMetadata), notes: availableNotes, onNotesLoaded: setAvailableNotes, @@ -212,6 +228,7 @@ export function MobileApp() { const createFlow = useMobileNoteCreateFlow({ createNote: (title) => createDemoVaultNote({ title, vaultMetadata: activeVaultMetadata }), onCreated: (note) => { + markGitLocalChanges() setAvailableNotes((notes) => [note, ...notes.filter((item) => item.id !== note.id)]) selectNoteId(note.id) }, @@ -219,11 +236,17 @@ export function MobileApp() { const propertiesFlow = useMobileNotePropertiesFlow({ loadNotes: () => loadDemoVaultNotes(activeVaultMetadata), onNotesLoaded: setAvailableNotes, - saveFrontmatter: (noteId, metadata) => saveDemoVaultNoteFrontmatter({ - metadata, - noteId, - vaultMetadata: activeVaultMetadata, - }), + saveFrontmatter: async (noteId, metadata) => { + const result = await saveDemoVaultNoteFrontmatter({ + metadata, + noteId, + vaultMetadata: activeVaultMetadata, + }) + if (result.status === 'saved') { + markGitLocalChanges() + } + return result + }, selectedNote, }) const toggleSelectedArchive = useCallback(() => { diff --git a/apps/mobile/src/mobileDemoVault.test.ts b/apps/mobile/src/mobileDemoVault.test.ts new file mode 100644 index 00000000..f2b5e03c --- /dev/null +++ b/apps/mobile/src/mobileDemoVault.test.ts @@ -0,0 +1,16 @@ +import { describe, expect, it } from 'vitest' +import { shouldSeedDemoVault } from './mobileDemoVaultSeedPolicy' + +describe('mobile demo vault loading', () => { + it('does not seed demo notes into remote-backed vaults', () => { + expect(shouldSeedDemoVault({ + id: 'personal', + name: 'Personal Journal', + remoteUrl: 'https://github.com/refactoringhq/tolaria.git', + })).toBe(false) + }) + + it('keeps local-only vaults seeded for first-run simulator QA', () => { + expect(shouldSeedDemoVault({ id: 'personal', name: 'Personal Journal' })).toBe(true) + }) +}) diff --git a/apps/mobile/src/mobileDemoVault.ts b/apps/mobile/src/mobileDemoVault.ts index 5ee7fe74..4581be9f 100644 --- a/apps/mobile/src/mobileDemoVault.ts +++ b/apps/mobile/src/mobileDemoVault.ts @@ -13,12 +13,15 @@ import { createNativeMobileVaultStorage } from './mobileNativeVaultStorage' import { createStoredMobileVaultRepository } from './mobileVaultRepository' import { seedMobileVaultIfEmpty } from './mobileVaultSeed' import type { MobileVaultFile } from './mobileVaultStorage' +import { shouldSeedDemoVault } from './mobileDemoVaultSeedPolicy' export async function loadDemoVaultNotes(vaultMetadata = defaultMobileVaultMetadata) { const storage = createNativeMobileVaultStorage() const demoVault = createDemoVaultConfig(vaultMetadata) - await seedMobileVaultIfEmpty({ files: demoVaultFiles(), storage, vault: demoVault }) - await addMissingDemoVaultFiles({ files: demoVaultFiles(), storage, vault: demoVault }) + if (shouldSeedDemoVault(vaultMetadata)) { + await seedMobileVaultIfEmpty({ files: demoVaultFiles(), storage, vault: demoVault }) + await addMissingDemoVaultFiles({ files: demoVaultFiles(), storage, vault: demoVault }) + } return createStoredMobileVaultRepository({ storage, vault: demoVault }).listNotes() } @@ -80,6 +83,7 @@ function createDemoVaultConfig(vaultMetadata: MobileVaultMetadata) { return createMobileVaultConfigFromMetadata(vaultMetadata) } + function createDemoVaultStorageContext(vaultMetadata: MobileVaultMetadata) { return { storage: createNativeMobileVaultStorage(), diff --git a/apps/mobile/src/mobileDemoVaultSeedPolicy.ts b/apps/mobile/src/mobileDemoVaultSeedPolicy.ts new file mode 100644 index 00000000..7b88c854 --- /dev/null +++ b/apps/mobile/src/mobileDemoVaultSeedPolicy.ts @@ -0,0 +1,5 @@ +import type { MobileVaultMetadata } from './mobileVaultMetadata' + +export function shouldSeedDemoVault(vaultMetadata: MobileVaultMetadata) { + return !vaultMetadata.remoteUrl?.trim() +} diff --git a/apps/mobile/src/mobileExpoGitFileSystem.ts b/apps/mobile/src/mobileExpoGitFileSystem.ts new file mode 100644 index 00000000..72adfe59 --- /dev/null +++ b/apps/mobile/src/mobileExpoGitFileSystem.ts @@ -0,0 +1,310 @@ +import { Buffer } from 'buffer' +import { createMobileVaultConfigFromMetadata } from './mobileVaultMetadata' +import type { MobileVaultMetadata } from './mobileVaultMetadata' +import type { ExpoMobileVaultFileInfo, ExpoMobileVaultFileSystem } from './mobileExpoVaultStorage' +import type { PromiseFsClient } from 'isomorphic-git' + +type MobileGitStat = { + ctime: Date + ctimeMs: number + dev: number + gid: number + ino: number + isDirectory: () => boolean + isFile: () => boolean + isSymbolicLink: () => boolean + mode: number + mtime: Date + mtimeMs: number + size: number + uid: number +} + +type MobileGitReadOptions = 'utf8' | { encoding?: 'base64' | 'utf8' } +type MobileGitWriteOptions = { encoding?: 'base64' | 'utf8' } +type MobileGitFileData = string | Uint8Array +type ExistingPathInput = { + fileSystem: ExpoMobileVaultFileSystem + path: string + rootUri: string +} + +export type MobileExpoGitFileSystemContext = { + dir: string + fs: PromiseFsClient +} + +export function createMobileExpoGitFileSystem({ + fileSystem, + vault, +}: { + fileSystem: ExpoMobileVaultFileSystem + vault: MobileVaultMetadata +}): MobileExpoGitFileSystemContext { + const rootUri = mobileExpoGitVaultRootUri({ fileSystem, vault }) + const dir = '/vault' + + return { + dir, + fs: { + promises: { + chmod: async () => {}, + lstat: (path: string) => statPath({ fileSystem, path, rootUri }), + mkdir: (path: string) => mkdirPath({ fileSystem, path, rootUri }), + readFile: (path: string, options?: MobileGitReadOptions) => readFile({ fileSystem, options, path, rootUri }), + readlink: async () => { + throw createFileSystemError('ENOTSUP', 'Symbolic links are not supported in mobile vaults.') + }, + readdir: (path: string) => readDirectory({ fileSystem, path, rootUri }), + rmdir: (path: string) => removeDirectory({ fileSystem, path, rootUri }), + stat: (path: string) => statPath({ fileSystem, path, rootUri }), + symlink: async () => { + throw createFileSystemError('ENOTSUP', 'Symbolic links are not supported in mobile vaults.') + }, + unlink: (path: string) => unlinkPath({ fileSystem, path, rootUri }), + writeFile: (path: string, data: MobileGitFileData, options?: MobileGitWriteOptions) => + writeFile({ data, fileSystem, options, path, rootUri }), + }, + }, + } +} + +export function mobileExpoGitVaultRootUri({ + fileSystem, + vault, +}: { + fileSystem: ExpoMobileVaultFileSystem + vault: MobileVaultMetadata +}) { + if (!fileSystem.documentDirectory) { + throw new Error('Expo FileSystem documentDirectory is unavailable') + } + + const vaultConfig = createMobileVaultConfigFromMetadata(vault) + return appendUri({ + root: fileSystem.documentDirectory, + segments: ['vaults', vaultConfig.storage.directoryName || vaultConfig.id], + }) +} + +async function mkdirPath({ + fileSystem, + path, + rootUri, +}: { + fileSystem: ExpoMobileVaultFileSystem + path: string + rootUri: string +}) { + const uri = uriForPath({ path, rootUri }) + const info = await fileSystem.getInfoAsync(uri) + if (info.exists && !info.isDirectory) { + throw createFileSystemError('ENOTDIR', `Path is not a directory: ${path}`) + } + + if (!info.exists) { + await fileSystem.makeDirectoryAsync(uri, { intermediates: true }) + } +} + +async function readDirectory({ + fileSystem, + path, + rootUri, +}: ExistingPathInput) { + await existingDirectory({ fileSystem, path, rootUri }) + + return fileSystem.readDirectoryAsync(uriForPath({ path, rootUri })) +} + +async function readFile({ + fileSystem, + options, + path, + rootUri, +}: { + fileSystem: ExpoMobileVaultFileSystem + options?: MobileGitReadOptions + path: string + rootUri: string +}) { + const info = await existingInfo({ fileSystem, path, rootUri }) + if (info.isDirectory) { + throw createFileSystemError('EISDIR', `Path is a directory: ${path}`) + } + + const uri = uriForPath({ path, rootUri }) + return readAsUtf8(options) + ? fileSystem.readAsStringAsync(uri, { encoding: 'utf8' }) + : Buffer.from(await fileSystem.readAsStringAsync(uri, { encoding: 'base64' }), 'base64') +} + +async function writeFile({ + data, + fileSystem, + options, + path, + rootUri, +}: { + data: MobileGitFileData + fileSystem: ExpoMobileVaultFileSystem + options?: MobileGitWriteOptions + path: string + rootUri: string +}) { + await ensureParentDirectory({ fileSystem, path, rootUri }) + + if (typeof data === 'string' && writeAsUtf8(options)) { + await fileSystem.writeAsStringAsync(uriForPath({ path, rootUri }), data, { encoding: 'utf8' }) + return + } + + await fileSystem.writeAsStringAsync( + uriForPath({ path, rootUri }), + dataToBase64(data), + { encoding: 'base64' }, + ) +} + +async function unlinkPath({ + fileSystem, + path, + rootUri, +}: ExistingPathInput) { + await existingFile({ fileSystem, path, rootUri }) + + await fileSystem.deleteAsync(uriForPath({ path, rootUri })) +} + +async function removeDirectory({ + fileSystem, + path, + rootUri, +}: ExistingPathInput) { + await existingDirectory({ fileSystem, path, rootUri }) + + await fileSystem.deleteAsync(uriForPath({ path, rootUri })) +} + +async function statPath({ + fileSystem, + path, + rootUri, +}: { + fileSystem: ExpoMobileVaultFileSystem + path: string + rootUri: string +}): Promise { + return statForInfo(await existingInfo({ fileSystem, path, rootUri })) +} + +async function existingInfo({ + fileSystem, + path, + rootUri, +}: ExistingPathInput) { + const info = await fileSystem.getInfoAsync(uriForPath({ path, rootUri })) + if (!info.exists) { + throw createFileSystemError('ENOENT', `Path does not exist: ${path}`) + } + + return info +} + +async function existingDirectory(input: ExistingPathInput) { + const info = await existingInfo(input) + if (!info.isDirectory) { + throw createFileSystemError('ENOTDIR', `Path is not a directory: ${input.path}`) + } +} + +async function existingFile(input: ExistingPathInput) { + const info = await existingInfo(input) + if (info.isDirectory) { + throw createFileSystemError('EISDIR', `Path is a directory: ${input.path}`) + } +} + +function statForInfo(info: ExpoMobileVaultFileInfo): MobileGitStat { + const modifiedMs = Math.round((info.modificationTime ?? 0) * 1000) + const modified = new Date(modifiedMs) + const isDirectory = info.isDirectory === true + + return { + ctime: modified, + ctimeMs: modifiedMs, + dev: 0, + gid: 0, + ino: 0, + isDirectory: () => isDirectory, + isFile: () => !isDirectory, + isSymbolicLink: () => false, + mode: isDirectory ? 0o040000 : 0o100644, + mtime: modified, + mtimeMs: modifiedMs, + size: info.size ?? 0, + uid: 0, + } +} + +async function ensureParentDirectory({ + fileSystem, + path, + rootUri, +}: { + fileSystem: ExpoMobileVaultFileSystem + path: string + rootUri: string +}) { + const segments = normalizedSegments(path) + const parentSegments = segments.slice(0, -1) + if (parentSegments.length === 0) { + return + } + + await fileSystem.makeDirectoryAsync(appendUri({ root: rootUri, segments: parentSegments }), { intermediates: true }) +} + +function uriForPath({ path, rootUri }: { path: string; rootUri: string }) { + return appendUri({ root: rootUri, segments: normalizedSegments(path) }) +} + +function normalizedSegments(path: string) { + const pathWithoutRoot = path.replace(/^\/+vault\/?/, '') + const segments = pathWithoutRoot.replaceAll('\\', '/').split('/').filter(Boolean) + if (segments.some(isUnsafeSegment)) { + throw createFileSystemError('EINVAL', `Unsafe mobile Git path: ${path}`) + } + + return segments +} + +function isUnsafeSegment(segment: string) { + return segment === '.' || segment === '..' || segment.includes('/') +} + +function readAsUtf8(options?: MobileGitReadOptions) { + return options === 'utf8' || (typeof options === 'object' && options.encoding === 'utf8') +} + +function writeAsUtf8(options?: MobileGitWriteOptions) { + return options?.encoding === 'utf8' +} + +function dataToBase64(data: MobileGitFileData) { + return typeof data === 'string' + ? Buffer.from(data, 'utf8').toString('base64') + : Buffer.from(data).toString('base64') +} + +function appendUri(input: { root: string; segments: string[] }) { + const base = input.root.replace(/\/+$/, '') + const path = input.segments.filter(Boolean).join('/') + + return path ? `${base}/${path}` : base +} + +function createFileSystemError(code: string, message: string) { + const error = new Error(message) + return Object.assign(error, { code }) +} diff --git a/apps/mobile/src/mobileExpoVaultStorage.ts b/apps/mobile/src/mobileExpoVaultStorage.ts index f92bd29b..4d5a177c 100644 --- a/apps/mobile/src/mobileExpoVaultStorage.ts +++ b/apps/mobile/src/mobileExpoVaultStorage.ts @@ -4,6 +4,8 @@ import type { MobileVaultFile, MobileVaultStorageDriver } from './mobileVaultSto export type ExpoMobileVaultFileInfo = { exists: boolean isDirectory?: boolean + modificationTime?: number + size?: number } export type ExpoMobileVaultFileSystem = { @@ -11,15 +13,21 @@ export type ExpoMobileVaultFileSystem = { documentDirectory: string | null getInfoAsync: (uri: string) => Promise makeDirectoryAsync: (uri: string, options: { intermediates: true }) => Promise - readAsStringAsync: (uri: string) => Promise + readAsStringAsync: (uri: string, options?: ExpoMobileVaultReadOptions) => Promise readDirectoryAsync: (uri: string) => Promise - writeAsStringAsync: (uri: string, content: string) => Promise + writeAsStringAsync: (uri: string, content: string, options?: ExpoMobileVaultWriteOptions) => Promise } type VaultPathInput = { path: string } +export type ExpoMobileVaultReadOptions = { + encoding?: 'base64' | 'utf8' +} + +export type ExpoMobileVaultWriteOptions = ExpoMobileVaultReadOptions + type DirectoryListingInput = { fileSystem: ExpoMobileVaultFileSystem rootUri: string diff --git a/apps/mobile/src/mobileGitAuthentication.test.ts b/apps/mobile/src/mobileGitAuthentication.test.ts index bd08f49a..bd4046bc 100644 --- a/apps/mobile/src/mobileGitAuthentication.test.ts +++ b/apps/mobile/src/mobileGitAuthentication.test.ts @@ -58,6 +58,7 @@ function memoryCredentialStorage(): MobileGitCredentialStorage { let isAvailable = false return { + loadRecord: async () => null, loadState: async () => isAvailable ? { state: 'available' } : { state: 'missing' }, remove: async () => { isAvailable = false @@ -70,6 +71,7 @@ function memoryCredentialStorage(): MobileGitCredentialStorage { function noopCredentialStorage(): MobileGitCredentialStorage { return { + loadRecord: async () => null, loadState: async () => ({ state: 'missing' }), remove: async () => {}, saveRecord: async () => {}, diff --git a/apps/mobile/src/mobileGitCredentialStateForVault.test.ts b/apps/mobile/src/mobileGitCredentialStateForVault.test.ts index abfd0eda..ade10781 100644 --- a/apps/mobile/src/mobileGitCredentialStateForVault.test.ts +++ b/apps/mobile/src/mobileGitCredentialStateForVault.test.ts @@ -35,6 +35,7 @@ function memoryCredentialStorage(): MobileGitCredentialStorage { const records = new Map>() return { + loadRecord: async (requirement) => records.get(`${requirement.strategy}:${requirement.host}`) ?? null, loadState: async (requirement) => records.has(`${requirement.strategy}:${requirement.host}`) ? { state: 'available' } : { state: 'missing' }, @@ -49,6 +50,9 @@ function memoryCredentialStorage(): MobileGitCredentialStorage { function failingCredentialStorage(): MobileGitCredentialStorage { return { + loadRecord: async () => { + throw new Error('should not load credentials') + }, loadState: async () => { throw new Error('should not load credentials') }, diff --git a/apps/mobile/src/mobileGitCredentialStorage.ts b/apps/mobile/src/mobileGitCredentialStorage.ts index ff2326ce..d4fd8e73 100644 --- a/apps/mobile/src/mobileGitCredentialStorage.ts +++ b/apps/mobile/src/mobileGitCredentialStorage.ts @@ -19,6 +19,7 @@ export type MobileGitCredentialRecord = { } export type MobileGitCredentialStorage = { + loadRecord: (requirement: MobileVaultAuthRequirement) => Promise loadState: (requirement: MobileVaultAuthRequirement) => Promise remove: (requirement: MobileVaultAuthRequirement) => Promise saveRecord: (record: MobileGitCredentialRecord) => Promise diff --git a/apps/mobile/src/mobileGitSyncFlowAction.test.ts b/apps/mobile/src/mobileGitSyncFlowAction.test.ts index 6f7c1c0e..5cd46238 100644 --- a/apps/mobile/src/mobileGitSyncFlowAction.test.ts +++ b/apps/mobile/src/mobileGitSyncFlowAction.test.ts @@ -55,6 +55,7 @@ function baseActionInput(events: string[]) { return { activeOperation: null, credentialStorage: { + loadRecord: async () => null, loadState: async () => ({ state: 'missing' as const }), remove: async () => {}, saveRecord: async () => {}, diff --git a/apps/mobile/src/mobileGitSyncFlowAction.ts b/apps/mobile/src/mobileGitSyncFlowAction.ts index 63945429..b80e3c4b 100644 --- a/apps/mobile/src/mobileGitSyncFlowAction.ts +++ b/apps/mobile/src/mobileGitSyncFlowAction.ts @@ -20,6 +20,7 @@ export function runMobileGitSyncFlowAction({ createGitHubOAuthSession, gitSyncPlan, gitTransport, + onSynced, refreshCredentials, setActiveOperation, setFailure, @@ -30,6 +31,7 @@ export function runMobileGitSyncFlowAction({ createGitHubOAuthSession: () => MobileGitHubOAuthSession gitSyncPlan: MobileGitSyncPlan gitTransport: MobileGitTransport + onSynced?: () => void refreshCredentials: () => void setActiveOperation: (operation: MobileGitOperation | null) => void setFailure: (failure: MobileGitSyncFlowFailure | null) => void @@ -55,6 +57,7 @@ export function runMobileGitSyncFlowAction({ if (action.state === 'transport') { runTransportAction({ gitTransport, + onSynced, operation: action.operation, remote: action.remote, setActiveOperation, @@ -103,6 +106,7 @@ function runAuthenticationAction({ function runTransportAction({ gitTransport, + onSynced, operation, remote, setActiveOperation, @@ -110,6 +114,7 @@ function runTransportAction({ vault, }: { gitTransport: MobileGitTransport + onSynced?: () => void operation: 'pull' | 'push' remote: Parameters[0]['remote'] setActiveOperation: (operation: MobileGitOperation | null) => void @@ -127,7 +132,10 @@ function runTransportAction({ .then((result) => { if (result.state === 'failed') { setFailure({ message: result.message, operation }) + return } + + onSynced?.() }) .catch(() => setFailure({ message: 'Mobile Git sync failed.', operation })) .finally(() => setActiveOperation(null)) diff --git a/apps/mobile/src/mobileGitSyncRuntimePlan.ts b/apps/mobile/src/mobileGitSyncRuntimePlan.ts index 106c1cff..d510bc92 100644 --- a/apps/mobile/src/mobileGitSyncRuntimePlan.ts +++ b/apps/mobile/src/mobileGitSyncRuntimePlan.ts @@ -10,11 +10,13 @@ import type { MobileVaultMetadata } from './mobileVaultMetadata' export function createMobileGitSyncPlanForVault({ credentials = { state: 'missing' }, failure, + hasLocalChanges = false, operation, vault, }: { credentials?: MobileGitCredentialState failure?: { message: string; operation: MobileGitOperation } + hasLocalChanges?: boolean operation?: MobileGitOperation vault: MobileVaultMetadata }): MobileGitSyncPlan { @@ -26,6 +28,7 @@ export function createMobileGitSyncPlanForVault({ return createMobileGitSyncPlan({ credentials, failure, + hasLocalChanges, operation, sync: result.config.sync, }) diff --git a/apps/mobile/src/mobileGitTransport.ts b/apps/mobile/src/mobileGitTransport.ts index f6aaedcd..93f5fe2c 100644 --- a/apps/mobile/src/mobileGitTransport.ts +++ b/apps/mobile/src/mobileGitTransport.ts @@ -16,9 +16,21 @@ export type MobileGitTransportResult = state: 'failed' } +export type MobileGitRepositoryStatus = + | { + hasLocalChanges: boolean + isRepository: boolean + state: 'available' + } + | { + message: string + state: 'failed' + } + export type MobileGitTransport = { pull: (request: MobileGitTransportRequest) => Promise push: (request: MobileGitTransportRequest) => Promise + status?: (request: MobileGitTransportRequest) => Promise } export function createUnavailableMobileGitTransport(): MobileGitTransport { diff --git a/apps/mobile/src/mobileIsomorphicGitTransport.test.ts b/apps/mobile/src/mobileIsomorphicGitTransport.test.ts new file mode 100644 index 00000000..3dc25909 --- /dev/null +++ b/apps/mobile/src/mobileIsomorphicGitTransport.test.ts @@ -0,0 +1,161 @@ +import { describe, expect, it } from 'vitest' +import type { StatusRow } from 'isomorphic-git' +import type { MobileGitCredentialStorage } from './mobileGitCredentialStorage' +import type { ExpoMobileVaultFileSystem } from './mobileExpoVaultStorage' +import { + createIsomorphicMobileGitTransport, + type MobileIsoGitClient, +} from './mobileIsomorphicGitTransport' +import type { MobileGitTransportRequest } from './mobileGitTransport' + +describe('isomorphic mobile git transport', () => { + it('clones a remote vault when the app-local directory is not a git repository', async () => { + const { events, transport } = transportFixture({ hasRepository: false }) + + await expect(transport.pull(request())).resolves.toEqual({ state: 'completed' }) + + expect(events).toEqual(['clone:https://github.com/refactoringhq/tolaria.git']) + }) + + it('pulls an existing app-local repository', async () => { + const { events, transport } = transportFixture({ hasRepository: true }) + + await expect(transport.pull(request())).resolves.toEqual({ state: 'completed' }) + + expect(events).toEqual(['pull']) + }) + + it('autocommits changed and deleted files before pushing', async () => { + const events: string[] = [] + const transport = createIsomorphicMobileGitTransport({ + credentialStorage: credentialStorage(), + fileSystem: fileSystem({ hasRepository: true }), + gitClient: gitClient({ + events, + statusRows: [ + ['changed.md', 1, 2, 1], + ['deleted.md', 1, 0, 1], + ], + }), + }) + + await expect(transport.push(request())).resolves.toEqual({ state: 'completed' }) + + expect(events).toEqual(['add:changed.md', 'remove:deleted.md', 'commit', 'push']) + }) + + it('reports dirty repository state for sync planning', async () => { + const transport = createIsomorphicMobileGitTransport({ + credentialStorage: credentialStorage(), + fileSystem: fileSystem({ hasRepository: true }), + gitClient: gitClient({ statusRows: [['changed.md', 1, 2, 1]] }), + }) + + await expect(transport.status?.(request())).resolves.toEqual({ + hasLocalChanges: true, + isRepository: true, + state: 'available', + }) + }) +}) + +function transportFixture({ + hasRepository, + statusRows, +}: { + hasRepository: boolean + statusRows?: StatusRow[] +}) { + const events: string[] = [] + const transport = createIsomorphicMobileGitTransport({ + credentialStorage: credentialStorage(), + fileSystem: fileSystem({ hasRepository }), + gitClient: gitClient({ events, statusRows }), + }) + + return { events, transport } +} + +function gitClient({ + events = [], + statusRows = [], +}: { + events?: string[] + statusRows?: StatusRow[] +} = {}): MobileIsoGitClient { + return { + add: async ({ filepath }) => { + events.push(`add:${filepath}`) + }, + clone: async ({ url }) => { + events.push(`clone:${url}`) + }, + commit: async () => { + events.push('commit') + return 'commit-oid' + }, + pull: async () => { + events.push('pull') + }, + push: async () => { + events.push('push') + return { error: null, ok: true } + }, + remove: async ({ filepath }) => { + events.push(`remove:${filepath}`) + }, + statusMatrix: async () => statusRows, + } +} + +function credentialStorage(): MobileGitCredentialStorage { + return { + loadRecord: async () => ({ + host: 'github.com', + kind: 'githubOAuthToken', + secret: { + accessToken: 'github-token', + tokenType: 'bearer', + }, + strategy: 'githubOAuth', + storedAt: '2026-05-12T12:00:00.000Z', + }), + loadState: async () => ({ state: 'available' }), + remove: async () => {}, + saveRecord: async () => {}, + } +} + +function fileSystem({ hasRepository }: { hasRepository: boolean }): ExpoMobileVaultFileSystem { + return { + deleteAsync: async () => {}, + documentDirectory: 'file:///docs/', + getInfoAsync: async (uri) => ({ + exists: uri === 'file:///docs/vaults/personal-journal' || (hasRepository && uri.endsWith('/.git')), + isDirectory: true, + modificationTime: 0, + size: 0, + }), + makeDirectoryAsync: async () => {}, + readAsStringAsync: async () => '', + readDirectoryAsync: async () => [], + writeAsStringAsync: async () => {}, + } +} + +function request(): MobileGitTransportRequest { + return { + remote: { + authStrategy: 'githubOAuth', + host: 'github.com', + owner: 'refactoringhq', + repository: 'tolaria', + url: 'https://github.com/refactoringhq/tolaria.git', + }, + vault: { + id: 'personal', + name: 'Personal Journal', + remoteUrl: 'https://github.com/refactoringhq/tolaria.git', + }, + } +} diff --git a/apps/mobile/src/mobileIsomorphicGitTransport.ts b/apps/mobile/src/mobileIsomorphicGitTransport.ts new file mode 100644 index 00000000..ae28801c --- /dev/null +++ b/apps/mobile/src/mobileIsomorphicGitTransport.ts @@ -0,0 +1,256 @@ +import * as git from 'isomorphic-git' +import http from 'isomorphic-git/http/web' +import type { AuthCallback, FsClient, HttpClient, StatusRow } from 'isomorphic-git' +import type { ExpoMobileVaultFileSystem } from './mobileExpoVaultStorage' +import { createMobileExpoGitFileSystem } from './mobileExpoGitFileSystem' +import type { MobileGitCredentialStorage } from './mobileGitCredentialStorage' +import type { MobileGitRemote } from './mobileGitRemote' +import type { + MobileGitRepositoryStatus, + MobileGitTransport, + MobileGitTransportRequest, + MobileGitTransportResult, +} from './mobileGitTransport' + +export type MobileIsoGitClient = { + add: (input: { dir: string; filepath: string; fs: FsClient }) => Promise + clone: (input: { depth?: number; dir: string; fs: FsClient; http: HttpClient; onAuth: AuthCallback; singleBranch: true; url: string }) => Promise + commit: (input: { + author: { email: string; name: string } + dir: string + fs: FsClient + message: string + }) => Promise + pull: (input: { + author: { email: string; name: string } + dir: string + fastForwardOnly: true + fs: FsClient + http: HttpClient + onAuth: AuthCallback + singleBranch: true + }) => Promise + push: (input: { dir: string; fs: FsClient; http: HttpClient; onAuth: AuthCallback }) => Promise<{ ok: boolean; error: string | null }> + remove: (input: { dir: string; filepath: string; fs: FsClient }) => Promise + statusMatrix: (input: { dir: string; fs: FsClient }) => Promise +} + +export function createIsomorphicMobileGitTransport({ + credentialStorage, + fileSystem, + gitClient = defaultGitClient, + httpClient = http, +}: { + credentialStorage: MobileGitCredentialStorage + fileSystem: ExpoMobileVaultFileSystem + gitClient?: MobileIsoGitClient + httpClient?: HttpClient +}): MobileGitTransport { + return { + pull: (request) => runGitOperation(() => pullOrClone({ credentialStorage, fileSystem, gitClient, httpClient, request })), + push: (request) => runGitOperation(() => commitAndPush({ credentialStorage, fileSystem, gitClient, httpClient, request })), + status: (request) => repositoryStatus({ fileSystem, gitClient, request }), + } +} + +async function pullOrClone({ + credentialStorage, + fileSystem, + gitClient, + httpClient, + request, +}: { + credentialStorage: MobileGitCredentialStorage + fileSystem: ExpoMobileVaultFileSystem + gitClient: MobileIsoGitClient + httpClient: HttpClient + request: MobileGitTransportRequest +}) { + const context = createMobileExpoGitFileSystem({ fileSystem, vault: request.vault }) + const onAuth = await authForRequest({ credentialStorage, remote: request.remote }) + + if (await isGitRepository(context)) { + await gitClient.pull({ + author: mobileGitAuthor(), + dir: context.dir, + fastForwardOnly: true, + fs: context.fs, + http: httpClient, + onAuth, + singleBranch: true, + }) + return + } + + await gitClient.clone({ + depth: 1, + dir: context.dir, + fs: context.fs, + http: httpClient, + onAuth, + singleBranch: true, + url: request.remote.url, + }) +} + +async function commitAndPush({ + credentialStorage, + fileSystem, + gitClient, + httpClient, + request, +}: { + credentialStorage: MobileGitCredentialStorage + fileSystem: ExpoMobileVaultFileSystem + gitClient: MobileIsoGitClient + httpClient: HttpClient + request: MobileGitTransportRequest +}) { + const context = createMobileExpoGitFileSystem({ fileSystem, vault: request.vault }) + if (!await isGitRepository(context)) { + throw new Error('Pull the remote vault before pushing mobile changes.') + } + + await commitLocalChanges({ context, gitClient }) + const result = await gitClient.push({ + dir: context.dir, + fs: context.fs, + http: httpClient, + onAuth: await authForRequest({ credentialStorage, remote: request.remote }), + }) + if (!result.ok) { + throw new Error(result.error ?? 'Git push failed.') + } +} + +async function repositoryStatus({ + fileSystem, + gitClient, + request, +}: { + fileSystem: ExpoMobileVaultFileSystem + gitClient: MobileIsoGitClient + request: MobileGitTransportRequest +}): Promise { + try { + const context = createMobileExpoGitFileSystem({ fileSystem, vault: request.vault }) + if (!await isGitRepository(context)) { + return { hasLocalChanges: false, isRepository: false, state: 'available' } + } + + return { + hasLocalChanges: hasChangedRows(await gitClient.statusMatrix({ dir: context.dir, fs: context.fs })), + isRepository: true, + state: 'available', + } + } catch { + return { message: 'Could not inspect mobile Git repository state.', state: 'failed' } + } +} + +async function commitLocalChanges({ + context, + gitClient, +}: { + context: MobileExpoGitContext + gitClient: MobileIsoGitClient +}) { + const matrix = await gitClient.statusMatrix({ dir: context.dir, fs: context.fs }) + const changedRows = matrix.filter(isChangedRow) + await Promise.all(changedRows.map((row) => stageStatusRow({ context, gitClient, row }))) + + if (changedRows.length > 0) { + await gitClient.commit({ + author: mobileGitAuthor(), + dir: context.dir, + fs: context.fs, + message: `Mobile sync ${new Date().toISOString()}`, + }) + } +} + +async function stageStatusRow({ + context, + gitClient, + row, +}: { + context: MobileExpoGitContext + gitClient: MobileIsoGitClient + row: StatusRow +}) { + const filepath = row[0] + if (row[2] === 0) { + await gitClient.remove({ dir: context.dir, filepath, fs: context.fs }) + return + } + + await gitClient.add({ dir: context.dir, filepath, fs: context.fs }) +} + +async function authForRequest({ + credentialStorage, + remote, +}: { + credentialStorage: MobileGitCredentialStorage + remote: MobileGitRemote +}): Promise { + if (remote.authStrategy !== 'githubOAuth') { + throw new Error('Mobile Git sync currently supports GitHub OAuth remotes only.') + } + + const record = await credentialStorage.loadRecord({ host: remote.host, strategy: remote.authStrategy }) + if (!record?.secret?.accessToken) { + throw new Error('GitHub credentials are missing. Connect GitHub before syncing.') + } + + return () => ({ username: record.secret?.accessToken }) +} + +async function isGitRepository(context: MobileExpoGitContext) { + try { + await context.fs.promises.stat(`${context.dir}/.git`) + return true + } catch { + return false + } +} + +async function runGitOperation(operation: () => Promise): Promise { + try { + await operation() + return { state: 'completed' } + } catch (error) { + return { message: gitFailureMessage(error), state: 'failed' } + } +} + +function hasChangedRows(rows: StatusRow[]) { + return rows.some(isChangedRow) +} + +function isChangedRow(row: StatusRow) { + return row[1] !== row[2] || row[2] !== row[3] +} + +function mobileGitAuthor() { + return { + email: 'mobile@tolaria.local', + name: 'Tolaria Mobile', + } +} + +function gitFailureMessage(error: unknown) { + return error instanceof Error ? error.message : 'Mobile Git sync failed.' +} + +type MobileExpoGitContext = ReturnType + +const defaultGitClient: MobileIsoGitClient = { + add: git.add, + clone: git.clone, + commit: git.commit, + pull: git.pull, + push: git.push, + remove: git.remove, + statusMatrix: git.statusMatrix, +} diff --git a/apps/mobile/src/mobileNativeIsomorphicGitTransport.ts b/apps/mobile/src/mobileNativeIsomorphicGitTransport.ts new file mode 100644 index 00000000..c9a7146b --- /dev/null +++ b/apps/mobile/src/mobileNativeIsomorphicGitTransport.ts @@ -0,0 +1,10 @@ +import * as ExpoFileSystem from 'expo-file-system/legacy' +import type { MobileGitCredentialStorage } from './mobileGitCredentialStorage' +import { createIsomorphicMobileGitTransport } from './mobileIsomorphicGitTransport' + +export function createNativeIsomorphicMobileGitTransport(credentialStorage: MobileGitCredentialStorage) { + return createIsomorphicMobileGitTransport({ + credentialStorage, + fileSystem: ExpoFileSystem, + }) +} diff --git a/apps/mobile/src/mobileSecureGitCredentialStorage.test.ts b/apps/mobile/src/mobileSecureGitCredentialStorage.test.ts index ae2166c1..d0826934 100644 --- a/apps/mobile/src/mobileSecureGitCredentialStorage.test.ts +++ b/apps/mobile/src/mobileSecureGitCredentialStorage.test.ts @@ -17,10 +17,15 @@ describe('createMobileSecureGitCredentialStorage', () => { })) await expect(storage.loadState(requirement)).resolves.toEqual({ state: 'available' }) + await expect(storage.loadRecord(requirement)).resolves.toMatchObject({ + host: 'github.com', + kind: 'githubOAuthToken', + }) await storage.remove(requirement) await expect(storage.loadState(requirement)).resolves.toEqual({ state: 'missing' }) + await expect(storage.loadRecord(requirement)).resolves.toBeNull() }) it('treats malformed secure-store content as missing credentials', async () => { diff --git a/apps/mobile/src/mobileSecureGitCredentialStorage.ts b/apps/mobile/src/mobileSecureGitCredentialStorage.ts index 54c5ec2a..740cdb3c 100644 --- a/apps/mobile/src/mobileSecureGitCredentialStorage.ts +++ b/apps/mobile/src/mobileSecureGitCredentialStorage.ts @@ -17,6 +17,8 @@ export function createMobileSecureGitCredentialStorage( secureStore: MobileSecureStore, ): MobileGitCredentialStorage { return { + loadRecord: async (requirement) => + parseMobileGitCredentialRecord(await secureStore.getItemAsync(createMobileGitCredentialKey(requirement))), loadState: async (requirement) => mobileGitCredentialState({ record: parseMobileGitCredentialRecord(await secureStore.getItemAsync(createMobileGitCredentialKey(requirement))), requirement, diff --git a/apps/mobile/src/useMobileGitSyncFlow.ts b/apps/mobile/src/useMobileGitSyncFlow.ts index 872b6d93..ddfc82ab 100644 --- a/apps/mobile/src/useMobileGitSyncFlow.ts +++ b/apps/mobile/src/useMobileGitSyncFlow.ts @@ -7,53 +7,161 @@ import { runMobileGitSyncFlowAction, type MobileGitSyncFlowFailure } from './mob import type { MobileGitTransport } from './mobileGitTransport' import type { MobileGitHubOAuthSession } from './mobileGitHubOAuthFlow' import type { MobileVaultMetadata } from './mobileVaultMetadata' +import { createMobileVaultConfig } from './mobileVaultConfig' export function useMobileGitSyncFlow({ createGitHubOAuthSession, credentialStorage, gitTransport, + onSynced, vault, }: { createGitHubOAuthSession: () => MobileGitHubOAuthSession credentialStorage: MobileGitCredentialStorage gitTransport: MobileGitTransport + onSynced?: () => void vault: MobileVaultMetadata }) { const [failure, setFailure] = useState(null) - const [credentials, setCredentials] = useState({ state: 'missing' }) const [activeOperation, setActiveOperation] = useState(null) + const { credentials, refreshCredentials } = useMobileGitCredentials({ credentialStorage, vault }) + const repositoryStatus = useMobileGitRepositoryStatus({ gitTransport, vault }) + const gitSyncPlan = useMemo( + () => createMobileGitSyncPlanForVault({ + credentials, + ...(failure ? { failure } : {}), + hasLocalChanges: repositoryStatus.hasLocalChanges, + ...(activeOperation ? { operation: activeOperation } : {}), + vault, + }), + [activeOperation, credentials, failure, repositoryStatus.hasLocalChanges, vault], + ) + const runPrimaryAction = useMobileGitPrimaryAction({ + activeOperation, + createGitHubOAuthSession, + credentialStorage, + gitSyncPlan, + gitTransport, + onSynced, + refreshCredentials, + repositoryStatus, + setActiveOperation, + setFailure, + vault, + }) + + useEffect(refreshCredentials, [refreshCredentials]) + useEffect(repositoryStatus.refresh, [repositoryStatus.refresh]) + + return { + gitSyncPlan, + markLocalChanges: repositoryStatus.markLocalChanges, + refreshRepositoryStatus: repositoryStatus.refresh, + runPrimaryAction, + } +} + +function useMobileGitCredentials({ + credentialStorage, + vault, +}: { + credentialStorage: MobileGitCredentialStorage + vault: MobileVaultMetadata +}) { + const [credentials, setCredentials] = useState({ state: 'missing' }) const refreshCredentials = useCallback(() => { void loadMobileGitCredentialStateForVault({ credentialStorage, vault }) .then(setCredentials) .catch(() => setCredentials({ state: 'missing' })) }, [credentialStorage, vault]) - const gitSyncPlan = useMemo( - () => createMobileGitSyncPlanForVault({ - credentials, - ...(failure ? { failure } : {}), - ...(activeOperation ? { operation: activeOperation } : {}), - vault, - }), - [activeOperation, credentials, failure, vault], - ) - const runPrimaryAction = useCallback(() => { + + return { credentials, refreshCredentials } +} + +function useMobileGitRepositoryStatus({ + gitTransport, + vault, +}: { + gitTransport: MobileGitTransport + vault: MobileVaultMetadata +}) { + const [hasLocalChanges, setHasLocalChanges] = useState(false) + const refresh = useCallback(() => { + const remote = mobileGitRemoteForVault(vault) + if (!gitTransport.status || !remote) { + setHasLocalChanges(false) + return + } + + void gitTransport.status({ remote, vault }) + .then((status) => setHasLocalChanges(status.state === 'available' && status.hasLocalChanges)) + .catch(() => setHasLocalChanges(false)) + }, [gitTransport, vault]) + const markLocalChanges = useCallback(() => setHasLocalChanges(true), []) + + return { hasLocalChanges, markLocalChanges, refresh, setHasLocalChanges } +} + +function useMobileGitPrimaryAction({ + activeOperation, + createGitHubOAuthSession, + credentialStorage, + gitSyncPlan, + gitTransport, + onSynced, + refreshCredentials, + repositoryStatus, + setActiveOperation, + setFailure, + vault, +}: { + activeOperation: MobileGitOperation | null + createGitHubOAuthSession: () => MobileGitHubOAuthSession + credentialStorage: MobileGitCredentialStorage + gitSyncPlan: ReturnType + gitTransport: MobileGitTransport + onSynced?: () => void + refreshCredentials: () => void + repositoryStatus: ReturnType + setActiveOperation: (operation: MobileGitOperation | null) => void + setFailure: (failure: MobileGitSyncFlowFailure | null) => void + vault: MobileVaultMetadata +}) { + return useCallback(() => { runMobileGitSyncFlowAction({ activeOperation, credentialStorage, createGitHubOAuthSession, gitSyncPlan, gitTransport, + onSynced: () => { + repositoryStatus.setHasLocalChanges(false) + repositoryStatus.refresh() + onSynced?.() + }, refreshCredentials, setActiveOperation, setFailure, vault, }) - }, [activeOperation, createGitHubOAuthSession, credentialStorage, gitSyncPlan, gitTransport, refreshCredentials, vault]) - - useEffect(refreshCredentials, [refreshCredentials]) - - return { + }, [ + activeOperation, + createGitHubOAuthSession, + credentialStorage, gitSyncPlan, - runPrimaryAction, - } + gitTransport, + onSynced, + refreshCredentials, + repositoryStatus, + setActiveOperation, + setFailure, + vault, + ]) +} + +function mobileGitRemoteForVault(vault: MobileVaultMetadata) { + const result = createMobileVaultConfig(vault) + return result.ok && result.config.sync.state === 'remoteReady' + ? result.config.sync.remote + : null } diff --git a/docs/ABSTRACTIONS.md b/docs/ABSTRACTIONS.md index e1a25089..416c7c6b 100644 --- a/docs/ABSTRACTIONS.md +++ b/docs/ABSTRACTIONS.md @@ -464,11 +464,11 @@ interface PulseCommit { ### Mobile Git Transport -The mobile app routes Git pull/push through `MobileGitTransport`. React Native UI and sync state code depend only on this TypeScript contract; the native implementation is introduced behind `createNativeMobileGitTransport`. +The mobile app routes Git pull/push through `MobileGitTransport`. React Native UI and sync state code depend only on this TypeScript contract; transport implementations can be JavaScript or native as long as they keep credentials behind the credential storage boundary and never place tokens in remote URLs. -`createNativeMobileGitTransport` resolves the optional Expo native module named `TolariaGit` through `expo-modules-core`. Until that module is wired into Expo development builds, the native transport adapter returns a clear unavailable-module failure. This keeps authentication, sync status, retry UI, and future libgit2/Rust work testable without pretending that a JavaScript fallback is production Git. +`createIsomorphicMobileGitTransport` is the current usable transport. It uses `isomorphic-git` plus an Expo FileSystem adapter rooted at the app-managed vault directory. For GitHub HTTPS remotes, it loads the OAuth token from SecureStore, clones the repository when `.git` is missing, uses fast-forward-only pull for existing repositories, and autocommits changed/deleted files before push. -The native boundary request is intentionally narrow for the first spike: active vault id, app-managed vault directory name, remote URL, remote host, and required auth strategy. Credentials stay behind SecureStore/OAuth/native Git credential callbacks, and remote URLs must not embed tokens. +`createNativeMobileGitTransport` still resolves the optional Expo native module named `TolariaGit` through `expo-modules-core`. That native boundary remains the later replacement path for SSH, non-GitHub remotes, conflict resolution, and performance work that proves unsuitable for the JavaScript transport. ### Auto-Sync diff --git a/docs/MOBILE_PROGRESS.md b/docs/MOBILE_PROGRESS.md index e24c2d92..2b8311a7 100644 --- a/docs/MOBILE_PROGRESS.md +++ b/docs/MOBILE_PROGRESS.md @@ -124,15 +124,18 @@ This file is the resumable working log for Tolaria mobile. The strategy and road - Tightened relationship writes so mobile relationship additions save canonical wikilink refs, dedupe by canonical target, resolve aliases/plain titles for chip display/opening, and avoid introducing new loose id/string values from the properties UI. - Demoted saved views from the primary mobile sidebar until real persisted view definitions exist; fixture nested-view definitions remain available to tests but are no longer exposed as if they were desktop-equivalent saved views. - Continued the properties quality pass by grouping the panel into system, relationships, custom properties, info, and history; adding read-only derived inverse relationship groups; and demoting custom scalar properties to read-only until typed mobile controls exist. +- Added the first usable mobile Git transport with `isomorphic-git` and an Expo FileSystem adapter: GitHub OAuth remotes can clone into app-local vault storage, fast-forward pull, autocommit local changes, and push. +- Disabled demo vault seeding for remote-backed vaults so cloned real vaults are not polluted with fixture notes. +- Created [ADR-0116](./adr/0116-isomorphic-git-for-mobile-real-vault-sync.md) for the JavaScript Git transport decision and its native-replacement boundary. - Set the booted iPad simulator keyboard preferences to Italian (`it_IT@sw=QWERTY;hw=Automatic`). ## Next Action Continue Phase 4 as a quality remediation pass before new feature work: -1. Run iPad simulator QA over sidebar filters, favorites, properties sections, relationship add/remove, raw wikilink autocomplete, and AI/settings input states. -2. Add real persisted view definition loading before re-exposing Views in the sidebar. -3. Implement usable native Git support so a real remote-backed vault can be cloned/synced locally. +1. Run simulator QA for the real-vault sync path: configure a GitHub remote, connect OAuth, clone, inspect loaded notes, edit locally, push, and relaunch. +2. Run iPad simulator QA over sidebar filters, favorites, properties sections, relationship add/remove, raw wikilink autocomplete, AI/settings input states, and Git status transitions. +3. Add real persisted view definition loading before re-exposing Views in the sidebar. 4. Install a simulator runtime matching the active Xcode SDK, or switch Xcode to one matching the installed iOS 17.5/18.6 runtimes, then retry the iOS development-client build. ## Verification Log @@ -444,6 +447,12 @@ Continue Phase 4 as a quality remediation pass before new feature work: - CodeScene after the direct-create/properties editability update: `apps/mobile/src/MobileApp.tsx`, `apps/mobile/src/MobileEditablePropertyPickers.tsx`, `apps/mobile/src/MobileEditorAdapter.tsx`, `apps/mobile/src/MobileEditorBreadcrumb.tsx`, `apps/mobile/src/MobilePropertiesPanel.tsx`, `apps/mobile/src/mobileNoteProperties.ts`, `apps/mobile/src/mobilePropertyPicker.ts`, `apps/mobile/src/styles/breadcrumbStyles.ts`, `apps/mobile/src/styles/commonStyles.ts`, `apps/mobile/src/styles/editorStyles.ts`, `apps/mobile/src/styles/noteListStyles.ts`, `apps/mobile/src/styles/propertyChipStyles.ts`, and `apps/mobile/src/useMobileNoteCreateFlow.ts` scored `10`; `apps/mobile/src/styles.ts` reported no scorable score. - CodeScene pre-commit safeguard passed after splitting the new styles. - `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export` passed after the direct-create/properties editability update. +- `pnpm --filter @tolaria/mobile exec expo install --check` passed after aligning Expo SDK 55 patch dependencies and adding the mobile Git transport dependencies. +- `pnpm --filter @tolaria/mobile typecheck` passed after the isomorphic-git mobile sync transport. +- `pnpm --filter @tolaria/mobile test` passed after the isomorphic-git mobile sync transport: 59 files / 191 tests. +- CodeScene after the isomorphic-git mobile sync transport: `apps/mobile/src/MobileApp.tsx`, `apps/mobile/src/mobileIsomorphicGitTransport.ts`, `apps/mobile/src/mobileExpoGitFileSystem.ts`, `apps/mobile/src/useMobileGitSyncFlow.ts`, `apps/mobile/src/mobileGitSyncFlowAction.ts`, `apps/mobile/src/mobileGitTransport.ts`, `apps/mobile/src/mobileIsomorphicGitTransport.test.ts`, `apps/mobile/src/mobileNativeIsomorphicGitTransport.ts`, `apps/mobile/src/mobileDemoVaultSeedPolicy.ts`, `apps/mobile/src/mobileDemoVault.test.ts`, `apps/mobile/src/mobileSecureGitCredentialStorage.ts`, `apps/mobile/src/mobileDemoVault.ts`, `apps/mobile/src/mobileGitCredentialStorage.ts`, and the touched credential/sync tests scored `10`; `apps/mobile/src/mobileGitSyncRuntimePlan.ts` reported no scorable score. +- CodeScene pre-commit safeguard passed after the isomorphic-git mobile sync transport. +- `pnpm --filter @tolaria/mobile exec expo export --platform ios --output-dir /tmp/tolaria-mobile-export-git-sync` passed after the isomorphic-git mobile sync transport. ## Risks / Watch Items diff --git a/docs/adr/0116-isomorphic-git-for-mobile-real-vault-sync.md b/docs/adr/0116-isomorphic-git-for-mobile-real-vault-sync.md new file mode 100644 index 00000000..67992b5a --- /dev/null +++ b/docs/adr/0116-isomorphic-git-for-mobile-real-vault-sync.md @@ -0,0 +1,29 @@ +# ADR-0116: Isomorphic-Git for Mobile Real-Vault Sync + +Date: 2026-05-12 + +## Status + +Accepted + +## Context + +Tolaria mobile needs a path to clone and sync a real vault before the native `TolariaGit` module is available. The Expo development-client build is still blocked locally by an Xcode simulator runtime mismatch, but the product needs simulator-testable vault sync now so iPad and iPhone workflow quality can be evaluated against real Markdown content. + +The existing mobile architecture already has app-local vault storage through Expo FileSystem, OAuth through Expo AuthSession, and GitHub tokens stored in SecureStore. What is missing is a Git transport that can use those boundaries without shelling out to terminal Git. + +## Decision + +Use `isomorphic-git` as the first mobile Git implementation behind the existing `MobileGitTransport` contract. Add a small Expo FileSystem adapter that maps the app-managed vault directory to the promise-style filesystem API expected by `isomorphic-git`. + +The first production path supports GitHub HTTPS remotes authenticated by the stored OAuth token. Pull clones the repository into app-local vault storage when `.git` is missing, then uses fast-forward-only pull for existing repositories. Push stages changed/deleted files, creates an automatic mobile checkpoint commit, and pushes through the same OAuth credential callback. + +Keep the native `TolariaGit` boundary available for later replacement if libgit2/Rust becomes necessary for performance, SSH, conflict handling, or full desktop parity. + +## Consequences + +- Real GitHub-backed vaults can be cloned and exercised in the simulator without waiting for a native build. +- OTA updates can improve the JavaScript transport and sync UX, as long as no new native dependency is introduced. +- The first sync path is GitHub OAuth over HTTPS only; SSH and arbitrary Git hosts remain a later native/credential-management slice. +- Conflict resolution remains deliberately out of scope for this slice. Pull is fast-forward-only so divergent histories fail visibly instead of inventing mobile merge behavior. +- The demo seed path must stay disabled for remote-backed vaults, otherwise cloned vaults would be polluted with fixture notes. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 58e1fdff..32e17e07 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -279,30 +279,36 @@ importers: '@tolaria/markdown': specifier: workspace:* version: link:../../packages/markdown + buffer: + specifier: ^6.0.3 + version: 6.0.3 expo: - specifier: ~55.0.20 - version: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + specifier: ~55.0.23 + version: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) expo-auth-session: specifier: ~55.0.15 - version: 55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + version: 55.0.15(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) expo-dev-client: specifier: ~55.0.32 - version: 55.0.32(expo@55.0.20) + version: 55.0.32(expo@55.0.23) expo-file-system: - specifier: 55.0.17 - version: 55.0.17(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + specifier: 55.0.19 + version: 55.0.19(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) expo-modules-core: - specifier: 55.0.24 - version: 55.0.24(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: 55.0.25 + version: 55.0.25(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) expo-secure-store: specifier: ~55.0.13 - version: 55.0.13(expo@55.0.20) + version: 55.0.13(expo@55.0.23) expo-status-bar: - specifier: ~55.0.5 - version: 55.0.5(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ~55.0.6 + version: 55.0.6(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) expo-web-browser: - specifier: ~55.0.14 - version: 55.0.14(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + specifier: ~55.0.15 + version: 55.0.15(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + isomorphic-git: + specifier: ^1.37.6 + version: 1.37.6 phosphor-react-native: specifier: ^3.0.6 version: 3.0.6(react-native-svg@15.15.3(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) @@ -1241,8 +1247,8 @@ packages: '@noble/hashes': optional: true - '@expo/cli@55.0.28': - resolution: {integrity: sha512-N3ZdMc5h9BZE2jn9O8xR2umJij+C7h1BMhZATw37zuTbh/JIcqBdfY5xRbpnMGw0VvNdK15nh89NYc8SzLUtow==} + '@expo/cli@55.0.29': + resolution: {integrity: sha512-r2dXQ82e/3nwxS7faLRL6HBD8UWDo/IyptQ0Vg6Z5Bgyp2Kd24h8xPn3RHfY3LLJ3wfEXglf4E79/Dqkm1Z6WA==} hasBin: true peerDependencies: expo: '*' @@ -1263,14 +1269,14 @@ packages: '@expo/config-types@55.0.5': resolution: {integrity: sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg==} - '@expo/config@55.0.15': - resolution: {integrity: sha512-lHc0ELIQ8126jYOMZpLv3WIuvordW98jFg5aT/J1/12n2ycuXu01XLZkJsdw0avO34cusUYb1It+MvY8JiMduA==} + '@expo/config@55.0.16': + resolution: {integrity: sha512-H5dpQv5TfyZDNheZAWO3SmP10diGWZwN5QOUsArkDJih0QKNtahQBOmrV2xbhgln/nrUGoy41U/ZIY/MEx63Ug==} '@expo/devcert@1.2.1': resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} - '@expo/devtools@55.0.2': - resolution: {integrity: sha512-4VsFn9MUriocyuhyA+ycJP3TJhUsOFHDc270l9h3LhNpXMf6wvIdGcA0QzXkZtORXmlDybWXRP2KT1k36HcQkA==} + '@expo/devtools@55.0.3': + resolution: {integrity: sha512-KoIDgo0NoXeWLsIcOdZqtAG/1LlsM+JL0DA3bo0vCYaOYTBLXi/ZvRBqa20Ub8D2vKLNa+FgRQW0gRg04Ps1Pg==} peerDependencies: react: '*' react-native: '*' @@ -1291,29 +1297,36 @@ packages: resolution: {integrity: sha512-rVvHC4I6xlPcg+mAO09ydUi2Wjv1ZytpLmHOSzvXzBAz9mMrJggqCe4s4dubjJvi/Ino/xQCLhbaLCnTtLpikg==} engines: {node: '>=20.12.0'} - '@expo/fingerprint@0.16.6': - resolution: {integrity: sha512-nRITNbnu3RKSHPvKVehrSU4KG2VY9V8nvULOHBw98ukHCAU4bGrU5APvcblOkX3JAap+xEHsg/mZvqlvkLInmQ==} + '@expo/env@2.1.2': + resolution: {integrity: sha512-RJtGFfj/ygO/6zcVbV3cckHf4THcEkv5IZft1GjCB3dfT6axvzvIwXE9EiQqQYmGHcQ+ZrvC8xZcIhiHba0pYg==} + engines: {node: '>=20.12.0'} + + '@expo/fingerprint@0.16.7': + resolution: {integrity: sha512-BH8sicYOqZ1iBMwCVEGIz6uTTfylosjc49FoMmCYIzKOiYdiVehsfoYBwyfxwWIiya1VMhm1gv0cgOP8fxHpDw==} hasBin: true - '@expo/image-utils@0.8.13': - resolution: {integrity: sha512-1I//yBQeTY6p0u1ihqGNDAr35EbSG8uFEupFrIF0jd++h9EWH33521yZJU1yE+mwGlzCb61g3ehu78siMhXBlA==} + '@expo/image-utils@0.8.14': + resolution: {integrity: sha512-5Sn+jG4Cw+shC2wDMXoqSAJnvERbiwzHn05FpWtD5IBflfTIs5gUmjzwiGVyjOdlMSQhgRrw/AymPbmO9h9mpQ==} '@expo/json-file@10.0.13': resolution: {integrity: sha512-pX/XjQn7tgNw6zuuV2ikmegmwe/S7uiwhrs2wXrANMkq7ozrA+JcZwgW9Q/8WZgciBzfAhNp5hnackHcrmapQA==} - '@expo/local-build-cache-provider@55.0.11': - resolution: {integrity: sha512-rJ4RTCrkeKaXaido/bVyhl90ZRtVTOEbj59F1PWVjIEIVgjdlfc1J3VD9v7hEsbf/+8Tbr/PgvWhT6Visi5sLQ==} + '@expo/json-file@10.0.14': + resolution: {integrity: sha512-yWwBFywFv+SxkJp/pIzzA416JVYflNUh7pqQzgaA6nXDqRyK7KfrqVzk8PdUfDnqbBcaZZxpzNssfQZzp5KHrA==} - '@expo/log-box@55.0.11': - resolution: {integrity: sha512-JQHFLWkskIbJi6cxYMjErx8lQqfFJilDQLKmdTO3m3YkdmN9GE/CrzjOfVlCG0DGEGZJ90br0pGKvGPdXNsHKw==} + '@expo/local-build-cache-provider@55.0.12': + resolution: {integrity: sha512-Wqhe7ajt6lyIEQvqDC1zm0MQ1RqQLlM9awCepY9pz+tm9rvhuxGPZTSddWeD8k4kolinBlDbLDFnNi06XgaDWQ==} + + '@expo/log-box@55.0.12': + resolution: {integrity: sha512-f9ARS8J60cq3LLNdIqmUjYwyerBzVS5Ecp7KjIf3GOIPjW0571rkcwLz4/U18l/1DeSkSzIkYsNl2TC9oTdWaQ==} peerDependencies: - '@expo/dom-webview': ^55.0.5 + '@expo/dom-webview': ^55.0.6 expo: '*' react: '*' react-native: '*' - '@expo/metro-config@55.0.19': - resolution: {integrity: sha512-tOeSR0w81F4wJxLm77Ee1FkaUUOfx7DuYgpoaOCBqODJOODX5fTydLjQPvezMQtFJubh2XWM9+4QoScqLTWbig==} + '@expo/metro-config@55.0.20': + resolution: {integrity: sha512-dUv0simEyPbN2wbOjI+BdEZyXdghgCZD0+3rrA1WxXZN1lRofUx6g2+Nik2Qg61v/BXFrCTh8reYEzQPzHOhdQ==} peerDependencies: expo: '*' peerDependenciesMeta: @@ -1323,38 +1336,41 @@ packages: '@expo/metro@55.1.1': resolution: {integrity: sha512-/wfXo5hTuAVpVLG/4hzlmD9NBGJkzkmBEMm/4VICajYRbj7y8OmqqPWbbymzHiBiHB6tI9BnsyXpQM6zVZEECg==} - '@expo/osascript@2.4.2': - resolution: {integrity: sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw==} + '@expo/osascript@2.4.3': + resolution: {integrity: sha512-wbuj3EebM7W9hN/Wp4xTzKd6rQ2zKJzAxkFxkOOwyysLp0HOAgQ4/5RINyoS241pZUX2rUHq7mAJ7pcCQ8U0Ow==} engines: {node: '>=12'} - '@expo/package-manager@1.10.4': - resolution: {integrity: sha512-y9Mr4Kmpk4abAVZrNNPCdzOZr8nLLyi18p1SXr0RCVA8IfzqZX/eY4H+50a0HTmXqIsPZrQdcdb4I3ekMS9GvQ==} + '@expo/package-manager@1.10.5': + resolution: {integrity: sha512-nCP9Mebfl3jvOr0/P6VAuyah6PAtun+aihIL2zAtuE8uSe94JWkVZ7051i0MUVO+y3gFpBqnr8IIH5ch+VJjHA==} '@expo/plist@0.5.2': resolution: {integrity: sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==} - '@expo/prebuild-config@55.0.16': - resolution: {integrity: sha512-o4EAVgDGk1lISirtMD8hciO2vyMp7cWlPdfTtjjd5AXSfODVYDIDhygXrfvVQHmJXAztVqPUTKJT+BYOsVkYGQ==} + '@expo/plist@0.5.3': + resolution: {integrity: sha512-jz5oPcPDd3fygwVxwSwmO6wodTwm0Qa14NUyPy0ka7H8sFmCtNZUI2+DzVe/EXjOhq1FbEjrwl89gdlWYOnVjQ==} + + '@expo/prebuild-config@55.0.17': + resolution: {integrity: sha512-Mcs+dg4Ripu0yCtzf66KZr18PehI1O8HxzJw+G5SUF8VWX+ic99aci1PltvmydWepLwTQL6ykmpXicAUA31IqA==} peerDependencies: expo: '*' - '@expo/require-utils@55.0.4': - resolution: {integrity: sha512-JAANvXqV7MOysWeVWgaiDzikoyDjJWOV/ulOW60Zb3kXJfrx2oZOtGtDXDFKD1mXuahQgoM5QOjuZhF7gFRNjA==} + '@expo/require-utils@55.0.5': + resolution: {integrity: sha512-U4K/CQ2VpXuwfNGsN+daKmYOt15hCP8v/pXaYH6eut7kdYZo6SfJ1yr67BIcJ+1Gzzs+QzTxswAZChKpXmceyw==} peerDependencies: typescript: ^5.0.0 || ^5.0.0-0 peerDependenciesMeta: typescript: optional: true - '@expo/router-server@55.0.15': - resolution: {integrity: sha512-6LksYO4Pg13qroL138KfUebt/x/EO07zVhdyT/nTgcxnpn6CS4ecTl3DciSKhxbaH+0BVLdANkxYeGdp43TMwQ==} + '@expo/router-server@55.0.16': + resolution: {integrity: sha512-LvAdrm039nQBG+95+ff5Rc4CsBuoc/giDhjQrgxB9lKJqC/ZTq1xbwfEZFNq6yokX6fOCs/vlxdhmSkOjMIrvg==} peerDependencies: - '@expo/metro-runtime': ^55.0.10 + '@expo/metro-runtime': ^55.0.11 expo: '*' - expo-constants: ^55.0.15 - expo-font: ^55.0.6 + expo-constants: ^55.0.16 + expo-font: ^55.0.7 expo-router: '*' - expo-server: ^55.0.8 + expo-server: ^55.0.9 react: '*' react-dom: '*' react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 @@ -1371,6 +1387,9 @@ packages: '@expo/schema-utils@55.0.3': resolution: {integrity: sha512-l9KHVjTo6MvoeyvwNr6AjckGJm8NIcqZ3QSAh51cWozXW9v2AUjyCyqYtFtyntLWRZ0x/ByYJishpQo4ZQq45Q==} + '@expo/schema-utils@55.0.4': + resolution: {integrity: sha512-65IdeeE8dAZR3n3J5Eq7LYiQ8BFGeEYCWPBCzycvafL7PkskbCyIclTQarRwf/HXFoRvezKCjaLwy/8v9Prk6g==} + '@expo/sdk-runtime-versions@1.0.0': resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} @@ -3575,6 +3594,7 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + deprecated: Potential CWE-502 - Update to 1.3.1 or higher '@upsetjs/venn.js@2.0.0': resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==} @@ -3749,9 +3769,16 @@ packages: ast-v8-to-istanbul@0.3.11: resolution: {integrity: sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==} + async-lock@1.4.1: + resolution: {integrity: sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ==} + asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + babel-jest@29.7.0: resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3801,12 +3828,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-expo@55.0.20: - resolution: {integrity: sha512-wMSaNtpPrH7+QNBF7HOCfO6WgmdXO84JEdqMhFxWc1CvwcWTvWwgc4yQustW1q1kG5ZE/dHgHkmHPlvVY5apMQ==} + babel-preset-expo@55.0.21: + resolution: {integrity: sha512-anXoUZBcxydLdVs2L+r3bWKGUvZv2FtgOl8xRJ12i/YfKICBpwTGZWSTiEYTqBByZ6GkA3mE9+3TW97X2ocFTQ==} peerDependencies: '@babel/runtime': ^7.20.0 expo: '*' - expo-widgets: ^55.0.16 + expo-widgets: ^55.0.17 react-refresh: '>=0.14.0 <1.0.0' peerDependenciesMeta: '@babel/runtime': @@ -3904,6 +3931,10 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} @@ -3987,6 +4018,9 @@ packages: classnames@2.5.1: resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} + clean-git-ref@2.0.1: + resolution: {integrity: sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw==} + cli-cursor@2.1.0: resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} engines: {node: '>=4'} @@ -4107,6 +4141,11 @@ packages: cose-base@2.2.0: resolution: {integrity: sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==} + crc-32@1.2.2: + resolution: {integrity: sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==} + engines: {node: '>=0.8'} + hasBin: true + crelt@1.0.6: resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==} @@ -4339,6 +4378,10 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -4349,6 +4392,10 @@ packages: defaults@1.0.4: resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + define-lazy-prop@2.0.0: resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} engines: {node: '>=8'} @@ -4382,6 +4429,9 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + diff3@0.0.3: + resolution: {integrity: sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g==} + dnssd-advertise@1.1.4: resolution: {integrity: sha512-AmGyK9WpNf06WeP5TjHZq/wNzP76OuEeaiTlKr9E/EEelYLczywUKoqRz+DPRq/ErssjT4lU+/W7wzJW+7K/ZA==} @@ -4599,8 +4649,8 @@ packages: peerDependencies: expo: '*' - expo-asset@55.0.16: - resolution: {integrity: sha512-5IJyfJtYqvKGg04NKGQWiCIoK/fULDL9m15mXPPyfabD1jsToVj2hnWmo1r2SWNNmMwtQxi6jTpcGwVo2nLDxg==} + expo-asset@55.0.17: + resolution: {integrity: sha512-pK9HHJuFqjE8kDUcbMFsZj3Cz8WdXpvZHZmYl7ouFQp59P83BvHln6VnqPDGlO+/4929G0Lm8ZUzbONuNRhi9w==} peerDependencies: expo: '*' react: '*' @@ -4618,6 +4668,12 @@ packages: expo: '*' react-native: '*' + expo-constants@55.0.16: + resolution: {integrity: sha512-Z15/No94UHoogD+pulxjudGAeOHTEIWZgb/vnX48Wx5D+apWTeCbnKxQZZtGQlosvduYL5kaic2/W8U+NHfBQQ==} + peerDependencies: + expo: '*' + react-native: '*' + expo-crypto@55.0.14: resolution: {integrity: sha512-TfAADBGZNNv9OOmdKFJCz54wDj87ufxtzQNSY+Roycpm8e5tuCnDIL7EjqUOmNTGH99Jj8ftPGFt4KGG2Ii2fg==} peerDependencies: @@ -4643,14 +4699,14 @@ packages: peerDependencies: expo: '*' - expo-file-system@55.0.17: - resolution: {integrity: sha512-d27K1cagUOt2BwxwPka9KW8Znu5kN1tnairozCzzCRZviZFtWnBxwFuJ3KU6MAbav/9UhSMkp5Ve/oZ+SR0UgQ==} + expo-file-system@55.0.19: + resolution: {integrity: sha512-c4smCbMqELLI3YQrGpw21MwZIREXM2e53vQD/+KWQcae1q+hgw8J2TroEqcQ/jVOtFpZYVvyVfgu4HDKNEKmNw==} peerDependencies: expo: '*' react-native: '*' - expo-font@55.0.6: - resolution: {integrity: sha512-x9czUA3UQWjIwa0ZUEs/eWJNqB4mAue/m4ltESlNPLZhHL0nWWqIfsyHmklTLFH7mVfcHSJvew6k+pR2FE1zVw==} + expo-font@55.0.7: + resolution: {integrity: sha512-oH39Xb+3i6Y69b7YRP+P+5WLx7621t+ep/RAgLwJJYpTjs7CnSohUG+873rEtqsTAuQGi63ms7x9ZeHj1E9LYw==} peerDependencies: expo: '*' react: '*' @@ -4659,8 +4715,8 @@ packages: expo-json-utils@55.0.2: resolution: {integrity: sha512-QJMOZOPOG7CTnKcrdVaiummn2va1MCO56z++eyWkDv3GBRODldM6MFMDf/jTREWthFc2Nxo6TuyWRrEV9S6n/Q==} - expo-keep-awake@55.0.7: - resolution: {integrity: sha512-QBWOEu8FkPBGYc0h0rsCkSTMJNBEKgzVsmLuQpO7V79V9sPR052k3Iiu/G8Kzmny2enyHYYed8RY+CUsip/SeQ==} + expo-keep-awake@55.0.8: + resolution: {integrity: sha512-PfIpMfM+STOBwkR5XOE+yVtER86c44MD+W8QD8JxuO0sT9pF7Y1SJYakWlpvX8xsGA+bjKLxftm9403s9kQhKA==} peerDependencies: expo: '*' react: '*' @@ -4676,12 +4732,12 @@ packages: peerDependencies: expo: '*' - expo-modules-autolinking@55.0.19: - resolution: {integrity: sha512-rHO1NZC/bxcKTLzkn6WYm9ErzS6qp7Kgb1NM2YxXJAYRWHwW/M7NZXyj6swWiKxyhRpcdoppRpjrz1sBuYGAjg==} + expo-modules-autolinking@55.0.21: + resolution: {integrity: sha512-P9KsJgOwI7JVwxmGfRvcXkXO4LNRvHRdWmb4ukLmX15G/vZ7b6SM17yiYkPceWq1F5KeeZ11KFjEcl0y17xy7w==} hasBin: true - expo-modules-core@55.0.24: - resolution: {integrity: sha512-1FztZjelwf3xQZpD6+LFo6IKjnGF/PMVXYkv9aC3EybMl/ZbXji35cfhy9W5uR/bwQ7L+SVqvd5A00XOoIiO8Q==} + expo-modules-core@55.0.25: + resolution: {integrity: sha512-yXpfg7aHLbuqoXocK34Vua6Aey5SCyqLygAsXAMbul9P8vfBjLpaOPiTJ5cLVF7Drfq8ownqVJO6qpGEtZ6GOw==} peerDependencies: react: '*' react-native: '*' @@ -4695,12 +4751,12 @@ packages: peerDependencies: expo: '*' - expo-server@55.0.8: - resolution: {integrity: sha512-AoV5TKuO4biSzrhe/OVLyInfTT0pV9/OOc/g/oVq5vmCjL8SaSYTkES8PLt+67Tm7VqX+Dn0+kSx1nQcjEKaPw==} + expo-server@55.0.9: + resolution: {integrity: sha512-N5Ipn1NwqaJzEm+G97o0Jbe4g/th3R/16N1DabnYryXKCiZwDkK13/w3VfGkQN9LOOaBP+JIRxGf4M8lQKPzyA==} engines: {node: '>=20.16.0'} - expo-status-bar@55.0.5: - resolution: {integrity: sha512-qb0c3rJO2b7CC0gUVGi1JYp92oLenWdYGyk8l4YQs6U+uaXUTPv6aaFa3KkT2HON10re3AxxPNJci8rsz6kPxg==} + expo-status-bar@55.0.6: + resolution: {integrity: sha512-ijOUptfdiqYt7rObZ6jrPQ8sE5YN/8MxKCIJx0b7TY4nGkSJxhPIxeoW4GXcXCA8mTQ9PiOHH/ThLZgRVZvUlQ==} peerDependencies: react: '*' react-native: '*' @@ -4710,14 +4766,14 @@ packages: peerDependencies: expo: '*' - expo-web-browser@55.0.14: - resolution: {integrity: sha512-bTDkBSQBnrlnYcM7Aak72AOvJuvdgA3M8p//Lazrm0Nfa77T9cRXzQ6KhLrB08V39n1+00d1dvuTWznJslkmdg==} + expo-web-browser@55.0.15: + resolution: {integrity: sha512-6hwZQob3EF+RWwZ+IvWLZjj2wI1frqx21+m/uzBqdUEHUhp2cVJi7kmxDolDmrve+ZldryZi1qfN78ALdvjHSA==} peerDependencies: expo: '*' react-native: '*' - expo@55.0.20: - resolution: {integrity: sha512-YY05NHHyEgHRWM1KswPkgeDnRssZ4S2H5Pwlt8q2NPVhXyhukHCxR8ighNndvSgqLo8yKzZMDyWlUZTPLyUERg==} + expo@55.0.23: + resolution: {integrity: sha512-b+lKwfzJzFiSm9G0wVGWw3c2YoZyubbl9gHOF1ZFuK8FqtxSge8pDDJMuEFmTi14dbKwh/tirB7MiORq54r7CQ==} hasBin: true peerDependencies: '@expo/dom-webview': '*' @@ -4841,6 +4897,10 @@ packages: fontfaceobserver@2.3.0: resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -4965,6 +5025,9 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -5179,6 +5242,10 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -5232,6 +5299,10 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + is-unicode-supported@1.3.0: resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} engines: {node: '>=12'} @@ -5244,9 +5315,17 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic-git@1.37.6: + resolution: {integrity: sha512-qr1NFCPsVTZ6YGqTXw0CzamnsHyH9QQ1OTEfeXIweSljRUMzuHFCJdUn0wc6OcjtTDns6knxjPb7N6LmJeftOA==} + engines: {node: '>=14.17'} + hasBin: true + isomorphic.js@0.2.5: resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} @@ -5854,6 +5933,10 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} + mimic-response@3.1.0: + 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'} @@ -5869,6 +5952,12 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minimisted@2.0.1: + resolution: {integrity: sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} @@ -6031,6 +6120,9 @@ packages: package-manager-detector@1.6.0: resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -6098,6 +6190,10 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pify@4.0.1: + resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==} + engines: {node: '>=6'} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -6133,6 +6229,10 @@ packages: points-on-path@0.2.1: resolution: {integrity: sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + postcss@8.5.10: resolution: {integrity: sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==} engines: {node: ^10 || ^12 || >=14} @@ -6657,9 +6757,18 @@ packages: resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} engines: {node: '>= 18'} + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sha.js@2.4.12: + resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} + engines: {node: '>= 0.10'} + hasBin: true + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -6698,6 +6807,12 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + simple-plist@1.3.1: resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} @@ -6909,6 +7024,10 @@ packages: tmpl@1.0.5: resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + to-buffer@1.2.2: + resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} + engines: {node: '>= 0.4'} + to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} @@ -6980,6 +7099,10 @@ packages: resolution: {integrity: sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==} engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + typescript-eslint@8.55.0: resolution: {integrity: sha512-HE4wj+r5lmDVS9gdaN0/+iqNvPZwGfnJ5lZuz7s5vLlg9ODw0bIiiETaios9LvFI1U94/VBXGm3CB2Y5cNFMpw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -7281,6 +7404,10 @@ packages: resolution: {integrity: sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + which@2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -8496,25 +8623,25 @@ snapshots: '@exodus/bytes@1.14.1': {} - '@expo/cli@55.0.28(@expo/dom-webview@55.0.5)(expo-constants@55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)))(expo-font@55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(expo@55.0.20)(react-dom@18.3.1(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@expo/cli@55.0.29(@expo/dom-webview@55.0.5)(expo-constants@55.0.16(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)))(expo-font@55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(expo@55.0.23)(react-dom@18.3.1(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': dependencies: '@expo/code-signing-certificates': 0.0.6 - '@expo/config': 55.0.15(typescript@5.9.3) + '@expo/config': 55.0.16(typescript@5.9.3) '@expo/config-plugins': 55.0.8 '@expo/devcert': 1.2.1 - '@expo/env': 2.1.1 - '@expo/image-utils': 0.8.13(typescript@5.9.3) - '@expo/json-file': 10.0.13 - '@expo/log-box': 55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/env': 2.1.2 + '@expo/image-utils': 0.8.14(typescript@5.9.3) + '@expo/json-file': 10.0.14 + '@expo/log-box': 55.0.12(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) '@expo/metro': 55.1.1 - '@expo/metro-config': 55.0.19(expo@55.0.20)(typescript@5.9.3) - '@expo/osascript': 2.4.2 - '@expo/package-manager': 1.10.4 - '@expo/plist': 0.5.2 - '@expo/prebuild-config': 55.0.16(expo@55.0.20)(typescript@5.9.3) - '@expo/require-utils': 55.0.4(typescript@5.9.3) - '@expo/router-server': 55.0.15(expo-constants@55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)))(expo-font@55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(expo-server@55.0.8)(expo@55.0.20)(react-dom@18.3.1(react@19.2.0))(react@19.2.0) - '@expo/schema-utils': 55.0.3 + '@expo/metro-config': 55.0.20(expo@55.0.23)(typescript@5.9.3) + '@expo/osascript': 2.4.3 + '@expo/package-manager': 1.10.5 + '@expo/plist': 0.5.3 + '@expo/prebuild-config': 55.0.17(expo@55.0.23)(typescript@5.9.3) + '@expo/require-utils': 55.0.5(typescript@5.9.3) + '@expo/router-server': 55.0.16(expo-constants@55.0.16(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)))(expo-font@55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(expo-server@55.0.9)(expo@55.0.23)(react-dom@18.3.1(react@19.2.0))(react@19.2.0) + '@expo/schema-utils': 55.0.4 '@expo/spawn-async': 1.7.2 '@expo/ws-tunnel': 1.0.6 '@expo/xcpretty': 4.4.3 @@ -8530,8 +8657,8 @@ snapshots: connect: 3.7.0 debug: 4.4.3 dnssd-advertise: 1.1.4 - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-server: 55.0.8 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-server: 55.0.9 fetch-nodeshim: 0.4.10 getenv: 2.0.0 glob: 13.0.6 @@ -8595,12 +8722,12 @@ snapshots: '@expo/config-types@55.0.5': {} - '@expo/config@55.0.15(typescript@5.9.3)': + '@expo/config@55.0.16(typescript@5.9.3)': dependencies: '@expo/config-plugins': 55.0.8 '@expo/config-types': 55.0.5 - '@expo/json-file': 10.0.13 - '@expo/require-utils': 55.0.4(typescript@5.9.3) + '@expo/json-file': 10.0.14 + '@expo/require-utils': 55.0.5(typescript@5.9.3) deepmerge: 4.3.1 getenv: 2.0.0 glob: 13.0.6 @@ -8618,16 +8745,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/devtools@55.0.2(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/devtools@55.0.3(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': dependencies: chalk: 4.1.2 optionalDependencies: react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) - '@expo/dom-webview@55.0.5(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/dom-webview@55.0.5(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) @@ -8639,9 +8766,17 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/fingerprint@0.16.6': + '@expo/env@2.1.2': dependencies: - '@expo/env': 2.1.1 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + transitivePeerDependencies: + - supports-color + + '@expo/fingerprint@0.16.7': + dependencies: + '@expo/env': 2.1.2 '@expo/spawn-async': 1.7.2 arg: 5.0.2 chalk: 4.1.2 @@ -8655,9 +8790,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/image-utils@0.8.13(typescript@5.9.3)': + '@expo/image-utils@0.8.14(typescript@5.9.3)': dependencies: - '@expo/require-utils': 55.0.4(typescript@5.9.3) + '@expo/require-utils': 55.0.5(typescript@5.9.3) '@expo/spawn-async': 1.7.2 chalk: 4.1.2 getenv: 2.0.0 @@ -8673,31 +8808,36 @@ snapshots: '@babel/code-frame': 7.29.0 json5: 2.2.3 - '@expo/local-build-cache-provider@55.0.11(typescript@5.9.3)': + '@expo/json-file@10.0.14': dependencies: - '@expo/config': 55.0.15(typescript@5.9.3) + '@babel/code-frame': 7.29.0 + json5: 2.2.3 + + '@expo/local-build-cache-provider@55.0.12(typescript@5.9.3)': + dependencies: + '@expo/config': 55.0.16(typescript@5.9.3) chalk: 4.1.2 transitivePeerDependencies: - supports-color - typescript - '@expo/log-box@55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/log-box@55.0.12(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': dependencies: - '@expo/dom-webview': 55.0.5(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/dom-webview': 55.0.5(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) anser: 1.4.10 - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) stacktrace-parser: 0.1.11 - '@expo/metro-config@55.0.19(expo@55.0.20)(typescript@5.9.3)': + '@expo/metro-config@55.0.20(expo@55.0.23)(typescript@5.9.3)': dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@expo/config': 55.0.15(typescript@5.9.3) - '@expo/env': 2.1.1 - '@expo/json-file': 10.0.13 + '@expo/config': 55.0.16(typescript@5.9.3) + '@expo/env': 2.1.2 + '@expo/json-file': 10.0.14 '@expo/metro': 55.1.1 '@expo/spawn-async': 1.7.2 browserslist: 4.28.1 @@ -8712,7 +8852,7 @@ snapshots: postcss: 8.5.10 resolve-from: 5.0.0 optionalDependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) transitivePeerDependencies: - bufferutil - supports-color @@ -8740,13 +8880,13 @@ snapshots: - supports-color - utf-8-validate - '@expo/osascript@2.4.2': + '@expo/osascript@2.4.3': dependencies: '@expo/spawn-async': 1.7.2 - '@expo/package-manager@1.10.4': + '@expo/package-manager@1.10.5': dependencies: - '@expo/json-file': 10.0.13 + '@expo/json-file': 10.0.14 '@expo/spawn-async': 1.7.2 chalk: 4.1.2 npm-package-arg: 11.0.3 @@ -8759,16 +8899,22 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 - '@expo/prebuild-config@55.0.16(expo@55.0.20)(typescript@5.9.3)': + '@expo/plist@0.5.3': dependencies: - '@expo/config': 55.0.15(typescript@5.9.3) + '@xmldom/xmldom': 0.8.13 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + '@expo/prebuild-config@55.0.17(expo@55.0.23)(typescript@5.9.3)': + dependencies: + '@expo/config': 55.0.16(typescript@5.9.3) '@expo/config-plugins': 55.0.8 '@expo/config-types': 55.0.5 - '@expo/image-utils': 0.8.13(typescript@5.9.3) - '@expo/json-file': 10.0.13 + '@expo/image-utils': 0.8.14(typescript@5.9.3) + '@expo/json-file': 10.0.14 '@react-native/normalize-colors': 0.83.6 debug: 4.4.3 - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) resolve-from: 5.0.0 semver: 7.7.4 xml2js: 0.6.0 @@ -8776,7 +8922,7 @@ snapshots: - supports-color - typescript - '@expo/require-utils@55.0.4(typescript@5.9.3)': + '@expo/require-utils@55.0.5(typescript@5.9.3)': dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 @@ -8786,13 +8932,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/router-server@55.0.15(expo-constants@55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)))(expo-font@55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(expo-server@55.0.8)(expo@55.0.20)(react-dom@18.3.1(react@19.2.0))(react@19.2.0)': + '@expo/router-server@55.0.16(expo-constants@55.0.16(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)))(expo-font@55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(expo-server@55.0.9)(expo@55.0.23)(react-dom@18.3.1(react@19.2.0))(react@19.2.0)': dependencies: debug: 4.4.3 - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) - expo-font: 55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-server: 55.0.8 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + expo-font: 55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + expo-server: 55.0.9 react: 19.2.0 optionalDependencies: react-dom: 18.3.1(react@19.2.0) @@ -8801,6 +8947,8 @@ snapshots: '@expo/schema-utils@55.0.3': {} + '@expo/schema-utils@55.0.4': {} + '@expo/sdk-runtime-versions@1.0.0': {} '@expo/spawn-async@1.7.2': @@ -8809,9 +8957,9 @@ snapshots: '@expo/sudo-prompt@9.3.2': {} - '@expo/vector-icons@15.1.1(expo-font@55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/vector-icons@15.1.1(expo-font@55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': dependencies: - expo-font: 55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + expo-font: 55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) @@ -11422,8 +11570,14 @@ snapshots: estree-walker: 3.0.3 js-tokens: 10.0.0 + async-lock@1.4.1: {} + asynckit@0.4.0: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + babel-jest@29.7.0(@babel/core@7.29.0): dependencies: '@babel/core': 7.29.0 @@ -11517,7 +11671,7 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) - babel-preset-expo@55.0.20(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.20)(react-refresh@0.14.2): + babel-preset-expo@55.0.21(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.23)(react-refresh@0.14.2): dependencies: '@babel/generator': 7.29.1 '@babel/helper-module-imports': 7.28.6 @@ -11545,7 +11699,7 @@ snapshots: resolve-from: 5.0.0 optionalDependencies: '@babel/runtime': 7.29.2 - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) transitivePeerDependencies: - '@babel/core' - supports-color @@ -11647,6 +11801,13 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 @@ -11730,6 +11891,8 @@ snapshots: classnames@2.5.1: {} + clean-git-ref@2.0.1: {} + cli-cursor@2.1.0: dependencies: restore-cursor: 2.0.0 @@ -11838,6 +12001,8 @@ snapshots: dependencies: layout-base: 2.0.1 + crc-32@1.2.2: {} + crelt@1.0.6: {} cross-spawn@7.0.6: @@ -12092,6 +12257,10 @@ snapshots: dependencies: character-entities: 2.0.2 + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + deep-is@0.1.4: {} deepmerge@4.3.1: {} @@ -12100,6 +12269,12 @@ snapshots: dependencies: clone: 1.0.4 + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + define-lazy-prop@2.0.0: {} delaunator@5.1.0: @@ -12122,6 +12297,8 @@ snapshots: dependencies: dequal: 2.0.3 + diff3@0.0.3: {} + dnssd-advertise@1.1.4: {} dom-accessibility-api@0.5.16: {} @@ -12354,28 +12531,28 @@ snapshots: expect-type@1.3.0: {} - expo-application@55.0.14(expo@55.0.20): + expo-application@55.0.14(expo@55.0.23): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-asset@55.0.16(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + expo-asset@55.0.17(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): dependencies: - '@expo/image-utils': 0.8.13(typescript@5.9.3) - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + '@expo/image-utils': 0.8.14(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) transitivePeerDependencies: - supports-color - typescript - expo-auth-session@55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-auth-session@55.0.15(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): dependencies: - expo-application: 55.0.14(expo@55.0.20) - expo-constants: 55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) - expo-crypto: 55.0.14(expo@55.0.20) - expo-linking: 55.0.14(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-web-browser: 55.0.14(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + expo-application: 55.0.14(expo@55.0.23) + expo-constants: 55.0.15(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + expo-crypto: 55.0.14(expo@55.0.23) + expo-linking: 55.0.14(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + expo-web-browser: 55.0.15(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) invariant: 2.2.4 react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) @@ -12383,65 +12560,73 @@ snapshots: - expo - supports-color - expo-constants@55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)): + expo-constants@55.0.15(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)): dependencies: '@expo/env': 2.1.1 - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) transitivePeerDependencies: - supports-color - expo-crypto@55.0.14(expo@55.0.20): + expo-constants@55.0.16(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@expo/env': 2.1.2 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) + transitivePeerDependencies: + - supports-color - expo-dev-client@55.0.32(expo@55.0.20): + expo-crypto@55.0.14(expo@55.0.23): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-dev-launcher: 55.0.33(expo@55.0.20) - expo-dev-menu: 55.0.27(expo@55.0.20) - expo-dev-menu-interface: 55.0.2(expo@55.0.20) - expo-manifests: 55.0.16(expo@55.0.20) - expo-updates-interface: 55.1.6(expo@55.0.20) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-dev-launcher@55.0.33(expo@55.0.20): + expo-dev-client@55.0.32(expo@55.0.23): + dependencies: + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-dev-launcher: 55.0.33(expo@55.0.23) + expo-dev-menu: 55.0.27(expo@55.0.23) + expo-dev-menu-interface: 55.0.2(expo@55.0.23) + expo-manifests: 55.0.16(expo@55.0.23) + expo-updates-interface: 55.1.6(expo@55.0.23) + + expo-dev-launcher@55.0.33(expo@55.0.23): dependencies: '@expo/schema-utils': 55.0.3 - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-dev-menu: 55.0.27(expo@55.0.20) - expo-manifests: 55.0.16(expo@55.0.20) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-dev-menu: 55.0.27(expo@55.0.23) + expo-manifests: 55.0.16(expo@55.0.23) - expo-dev-menu-interface@55.0.2(expo@55.0.20): + expo-dev-menu-interface@55.0.2(expo@55.0.23): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-dev-menu@55.0.27(expo@55.0.20): + expo-dev-menu@55.0.27(expo@55.0.23): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-dev-menu-interface: 55.0.2(expo@55.0.20) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-dev-menu-interface: 55.0.2(expo@55.0.23) - expo-file-system@55.0.17(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)): + expo-file-system@55.0.19(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) - expo-font@55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-font@55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) fontfaceobserver: 2.3.0 react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) expo-json-utils@55.0.2: {} - expo-keep-awake@55.0.7(expo@55.0.20)(react@19.2.0): + expo-keep-awake@55.0.8(expo@55.0.23)(react@19.2.0): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) react: 19.2.0 - expo-linking@55.0.14(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-linking@55.0.14(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): dependencies: - expo-constants: 55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + expo-constants: 55.0.15(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) invariant: 2.2.4 react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) @@ -12449,14 +12634,14 @@ snapshots: - expo - supports-color - expo-manifests@55.0.16(expo@55.0.20): + expo-manifests@55.0.16(expo@55.0.23): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) expo-json-utils: 55.0.2 - expo-modules-autolinking@55.0.19(typescript@5.9.3): + expo-modules-autolinking@55.0.21(typescript@5.9.3): dependencies: - '@expo/require-utils': 55.0.4(typescript@5.9.3) + '@expo/require-utils': 55.0.5(typescript@5.9.3) '@expo/spawn-async': 1.7.2 chalk: 4.1.2 commander: 7.2.0 @@ -12464,62 +12649,62 @@ snapshots: - supports-color - typescript - expo-modules-core@55.0.24(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-modules-core@55.0.25(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): dependencies: invariant: 2.2.4 react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) - expo-secure-store@55.0.13(expo@55.0.20): + expo-secure-store@55.0.13(expo@55.0.23): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-server@55.0.8: {} + expo-server@55.0.9: {} - expo-status-bar@55.0.5(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-status-bar@55.0.6(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): dependencies: react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) react-native-is-edge-to-edge: 1.3.1(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-updates-interface@55.1.6(expo@55.0.20): + expo-updates-interface@55.1.6(expo@55.0.23): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-web-browser@55.0.14(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)): + expo-web-browser@55.0.15(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)): dependencies: - expo: 55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) - expo@55.0.20(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + expo@55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(react-dom@18.3.1(react@19.2.0))(react-native-webview@13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 - '@expo/cli': 55.0.28(@expo/dom-webview@55.0.5)(expo-constants@55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)))(expo-font@55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(expo@55.0.20)(react-dom@18.3.1(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@expo/config': 55.0.15(typescript@5.9.3) + '@expo/cli': 55.0.29(@expo/dom-webview@55.0.5)(expo-constants@55.0.16(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)))(expo-font@55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(expo@55.0.23)(react-dom@18.3.1(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@expo/config': 55.0.16(typescript@5.9.3) '@expo/config-plugins': 55.0.8 - '@expo/devtools': 55.0.2(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@expo/fingerprint': 0.16.6 - '@expo/local-build-cache-provider': 55.0.11(typescript@5.9.3) - '@expo/log-box': 55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/devtools': 55.0.3(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/fingerprint': 0.16.7 + '@expo/local-build-cache-provider': 55.0.12(typescript@5.9.3) + '@expo/log-box': 55.0.12(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) '@expo/metro': 55.1.1 - '@expo/metro-config': 55.0.19(expo@55.0.20)(typescript@5.9.3) - '@expo/vector-icons': 15.1.1(expo-font@55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/metro-config': 55.0.20(expo@55.0.23)(typescript@5.9.3) + '@expo/vector-icons': 15.1.1(expo-font@55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) '@ungap/structured-clone': 1.3.0 - babel-preset-expo: 55.0.20(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.20)(react-refresh@0.14.2) - expo-asset: 55.0.16(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.15(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) - expo-file-system: 55.0.17(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) - expo-font: 55.0.6(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-keep-awake: 55.0.7(expo@55.0.20)(react@19.2.0) - expo-modules-autolinking: 55.0.19(typescript@5.9.3) - expo-modules-core: 55.0.24(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + babel-preset-expo: 55.0.21(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.23)(react-refresh@0.14.2) + expo-asset: 55.0.17(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + expo-file-system: 55.0.19(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0)) + expo-font: 55.0.7(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + expo-keep-awake: 55.0.8(expo@55.0.23)(react@19.2.0) + expo-modules-autolinking: 55.0.21(typescript@5.9.3) + expo-modules-core: 55.0.25(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) pretty-format: 29.7.0 react: 19.2.0 react-native: 0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0) react-refresh: 0.14.2 whatwg-url-minimum: 0.1.1 optionalDependencies: - '@expo/dom-webview': 55.0.5(expo@55.0.20)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/dom-webview': 55.0.5(expo@55.0.23)(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) react-native-webview: 13.16.0(react-native@0.83.6(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) transitivePeerDependencies: - '@babel/core' @@ -12668,6 +12853,10 @@ snapshots: fontfaceobserver@2.3.0: {} + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -12785,6 +12974,10 @@ snapshots: has-flag@4.0.0: {} + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + has-symbols@1.1.0: {} has-tostringtag@1.0.2: @@ -13073,6 +13266,8 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + is-callable@1.2.7: {} + is-core-module@2.16.1: dependencies: hasown: 2.0.2 @@ -13105,6 +13300,10 @@ snapshots: is-promise@4.0.0: {} + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + is-unicode-supported@1.3.0: {} is-unicode-supported@2.1.0: {} @@ -13113,8 +13312,24 @@ snapshots: dependencies: is-docker: 2.2.1 + isarray@2.0.5: {} + isexe@2.0.0: {} + isomorphic-git@1.37.6: + dependencies: + async-lock: 1.4.1 + clean-git-ref: 2.0.1 + crc-32: 1.2.2 + diff3: 0.0.3 + ignore: 5.3.2 + minimisted: 2.0.1 + pako: 1.0.11 + pify: 4.0.1 + readable-stream: 4.7.0 + sha.js: 2.4.12 + simple-get: 4.0.1 + isomorphic.js@0.2.5: {} istanbul-lib-coverage@3.2.2: {} @@ -14063,6 +14278,8 @@ snapshots: mimic-function@5.0.1: {} + mimic-response@3.1.0: {} + min-indent@1.0.1: {} minimatch@10.2.5: @@ -14077,6 +14294,12 @@ snapshots: dependencies: brace-expansion: 2.0.2 + minimist@1.2.8: {} + + minimisted@2.0.1: + dependencies: + minimist: 1.2.8 + minipass@7.1.3: {} mkdirp@1.0.4: {} @@ -14232,6 +14455,8 @@ snapshots: package-manager-detector@1.6.0: {} + pako@1.0.11: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -14291,6 +14516,8 @@ snapshots: picomatch@4.0.4: {} + pify@4.0.1: {} + pirates@4.0.7: {} pkce-challenge@5.0.1: {} @@ -14324,6 +14551,8 @@ snapshots: path-data-parser: 0.1.0 points-on-curve: 0.2.0 + possible-typed-array-names@1.1.0: {} + postcss@8.5.10: dependencies: nanoid: 3.3.11 @@ -15091,8 +15320,23 @@ snapshots: transitivePeerDependencies: - supports-color + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + setprototypeof@1.2.0: {} + sha.js@2.4.12: + dependencies: + inherits: 2.0.4 + safe-buffer: 5.2.1 + to-buffer: 1.2.2 + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -15135,6 +15379,14 @@ snapshots: signal-exit@4.1.0: {} + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + simple-plist@1.3.1: dependencies: bplist-creator: 0.1.0 @@ -15337,6 +15589,12 @@ snapshots: tmpl@1.0.5: {} + to-buffer@1.2.2: + dependencies: + isarray: 2.0.5 + safe-buffer: 5.2.1 + typed-array-buffer: 1.0.3 + to-regex-range@5.0.1: dependencies: is-number: 7.0.0 @@ -15389,6 +15647,12 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + typescript-eslint@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.55.0(@typescript-eslint/parser@8.55.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -15652,6 +15916,16 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0