diff --git a/apps/memoro/apps/server/package.json b/apps/memoro/apps/server/package.json index 573e4542e..5203776e1 100644 --- a/apps/memoro/apps/server/package.json +++ b/apps/memoro/apps/server/package.json @@ -1,21 +1,22 @@ { - "name": "@memoro/server", - "version": "1.0.0", - "private": true, - "scripts": { - "dev": "bun run --hot src/index.ts", - "start": "bun run src/index.ts", - "build": "bun build src/index.ts --outdir dist --target bun" - }, - "dependencies": { - "@manacore/shared-hono": "workspace:*", - "@supabase/supabase-js": "^2.49.5", - "hono": "^4.7.0", - "uuid": "^11.0.0" - }, - "devDependencies": { - "@types/node": "^20.0.0", - "@types/uuid": "^10.0.0", - "typescript": "^5.5.0" - } + "name": "@memoro/server", + "version": "1.0.0", + "private": true, + "scripts": { + "dev": "bun run --hot src/index.ts", + "start": "bun run src/index.ts", + "build": "bun build src/index.ts --outdir dist --target bun" + }, + "dependencies": { + "@manacore/shared-hono": "workspace:*", + "@supabase/supabase-js": "^2.49.5", + "hono": "^4.7.0", + "uuid": "^11.0.0", + "zod": "^3.24.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "@types/uuid": "^10.0.0", + "typescript": "^5.5.0" + } } diff --git a/apps/memoro/apps/server/src/lib/validate.ts b/apps/memoro/apps/server/src/lib/validate.ts new file mode 100644 index 000000000..2e9dccf67 --- /dev/null +++ b/apps/memoro/apps/server/src/lib/validate.ts @@ -0,0 +1,61 @@ +/** + * Zod validation helper for Hono route handlers. + */ + +import type { Context } from 'hono'; +import type { ZodType, ZodError, ZodTypeDef } from 'zod'; + +type ValidationResult = { success: true; data: T } | { success: false; response: Response }; + +function formatZodError(error: ZodError): string { + return error.issues.map((i) => i.message).join(', '); +} + +/** + * Validate JSON body against a Zod schema. + * Returns parsed data on success, or sends a 400 response on failure. + */ +export async function validateBody( + c: Context, + schema: ZodType +): Promise> { + let raw: unknown; + try { + raw = await c.req.json(); + } catch { + return { + success: false, + response: c.json({ success: false, error: 'Invalid JSON body' }, 400), + }; + } + + const result = schema.safeParse(raw); + if (!result.success) { + return { + success: false, + response: c.json({ success: false, error: formatZodError(result.error) }, 400), + }; + } + + return { success: true, data: result.data }; +} + +/** + * Validate query parameters against a Zod schema. + */ +export function validateQuery( + c: Context, + schema: ZodType +): ValidationResult { + const raw = c.req.query(); + + const result = schema.safeParse(raw); + if (!result.success) { + return { + success: false, + response: c.json({ success: false, error: formatZodError(result.error) }, 400), + }; + } + + return { success: true, data: result.data }; +} diff --git a/apps/memoro/apps/server/src/middleware/rate-limiter.ts b/apps/memoro/apps/server/src/middleware/rate-limiter.ts index d1d0782e5..3c2c416d2 100644 --- a/apps/memoro/apps/server/src/middleware/rate-limiter.ts +++ b/apps/memoro/apps/server/src/middleware/rate-limiter.ts @@ -31,7 +31,7 @@ export function rateLimiter(options: RateLimiterOptions = {}): MiddlewareHandler } }, 5 * 60_000); - return async (c, next) => { + return async (c, next): Promise => { const ip = c.req.header('x-forwarded-for')?.split(',')[0]?.trim() || c.req.header('x-real-ip') || diff --git a/apps/memoro/apps/server/src/routes/cleanup.ts b/apps/memoro/apps/server/src/routes/cleanup.ts index 128185b73..a9d21071c 100644 --- a/apps/memoro/apps/server/src/routes/cleanup.ts +++ b/apps/memoro/apps/server/src/routes/cleanup.ts @@ -7,6 +7,8 @@ import { Hono } from 'hono'; import { HTTPException } from 'hono/http-exception'; import { runAudioCleanup } from '../services/cleanup'; +import { validateBody } from '../lib/validate'; +import { manualCleanupBody } from '../schemas'; export const cleanupRoutes = new Hono(); @@ -28,9 +30,7 @@ cleanupRoutes.post('/run', async (c) => { // Run cleanup asynchronously and return immediately queueMicrotask(() => { - runAudioCleanup().catch((err) => - console.error('[cleanup] Background cleanup failed:', err) - ); + runAudioCleanup().catch((err) => console.error('[cleanup] Background cleanup failed:', err)); }); return c.json({ success: true, message: 'Cleanup started' }); @@ -38,8 +38,9 @@ cleanupRoutes.post('/run', async (c) => { // POST /manual — manual trigger with optional user IDs cleanupRoutes.post('/manual', async (c) => { - const body = await c.req.json<{ userIds?: string[] }>().catch(() => ({ userIds: undefined })); - const userIds = body.userIds ?? []; + const v = await validateBody(c, manualCleanupBody); + if (!v.success) return v.response; + const userIds = v.data.userIds ?? []; console.log( `[cleanup] Manual trigger${userIds.length > 0 ? ` for ${userIds.length} users` : ' for all opted-in users'}` @@ -50,6 +51,6 @@ cleanupRoutes.post('/manual', async (c) => { return c.json({ success: true, ...result }); } catch (err) { console.error('[cleanup] Manual cleanup failed:', err); - return c.json({ error: 'Cleanup failed' }, 500); + return c.json({ success: false, error: 'Cleanup failed' }, 500); } }); diff --git a/apps/memoro/apps/server/src/routes/credits.ts b/apps/memoro/apps/server/src/routes/credits.ts index d6f13f093..f674073f9 100644 --- a/apps/memoro/apps/server/src/routes/credits.ts +++ b/apps/memoro/apps/server/src/routes/credits.ts @@ -6,12 +6,14 @@ import { Hono } from 'hono'; import type { AuthVariables } from '@manacore/shared-hono'; import { validateCredits, consumeCredits, COSTS } from '../lib/credits'; import { getBalance } from '@manacore/shared-hono'; +import { validateBody } from '../lib/validate'; +import { checkCreditsBody, consumeCreditsBody } from '../schemas'; export const creditRoutes = new Hono<{ Variables: AuthVariables }>(); // GET /pricing — public, returns cost constants creditRoutes.get('/pricing', (c) => { - return c.json({ costs: COSTS }); + return c.json({ success: true, costs: COSTS }); }); // GET /balance — authenticated, returns user's credit balance @@ -19,56 +21,50 @@ creditRoutes.get('/balance', async (c) => { const userId = c.get('userId') as string; try { const balance = await getBalance(userId); - return c.json({ credits: balance.balance, totalEarned: balance.totalEarned, totalSpent: balance.totalSpent }); + return c.json({ + success: true, + credits: balance.balance, + totalEarned: balance.totalEarned, + totalSpent: balance.totalSpent, + }); } catch (err) { console.error('[credits] Balance error:', err); - return c.json({ error: 'Failed to fetch balance' }, 500); + return c.json({ success: false, error: 'Failed to fetch balance' }, 500); } }); -// POST /check — validate credits (requires auth via parent router) +// POST /check — validate credits creditRoutes.post('/check', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ operation: string; amount: number }>(); - - if (!body.operation || body.amount == null) { - return c.json({ error: 'operation and amount are required' }, 400); - } + const v = await validateBody(c, checkCreditsBody); + if (!v.success) return v.response; try { - const result = await validateCredits(userId, body.operation, body.amount); - return c.json(result); + const result = await validateCredits(userId, v.data.operation, v.data.amount); + return c.json({ success: true, ...result }); } catch (err) { console.error('[credits] Validate error:', err); - return c.json({ error: 'Failed to validate credits' }, 500); + return c.json({ success: false, error: 'Failed to validate credits' }, 500); } }); -// POST /consume — consume credits (requires auth via parent router) +// POST /consume — consume credits creditRoutes.post('/consume', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ - operation: string; - amount: number; - description: string; - metadata?: Record; - }>(); - - if (!body.operation || body.amount == null || !body.description) { - return c.json({ error: 'operation, amount, and description are required' }, 400); - } + const v = await validateBody(c, consumeCreditsBody); + if (!v.success) return v.response; try { - const success = await consumeCredits( + const result = await consumeCredits( userId, - body.operation, - body.amount, - body.description, - body.metadata + v.data.operation, + v.data.amount, + v.data.description, + v.data.metadata ); - return c.json({ success }); + return c.json({ success: true, consumed: result }); } catch (err) { console.error('[credits] Consume error:', err); - return c.json({ error: 'Failed to consume credits' }, 500); + return c.json({ success: false, error: 'Failed to consume credits' }, 500); } }); diff --git a/apps/memoro/apps/server/src/routes/internal.ts b/apps/memoro/apps/server/src/routes/internal.ts index 189d844a4..48df3a6cb 100644 --- a/apps/memoro/apps/server/src/routes/internal.ts +++ b/apps/memoro/apps/server/src/routes/internal.ts @@ -8,6 +8,12 @@ import { Hono } from 'hono'; import { HTTPException } from 'hono/http-exception'; import { handleTranscriptionCompleted } from '../services/memo'; import { createServiceClient } from '../lib/supabase'; +import { validateBody } from '../lib/validate'; +import { + transcriptionCompletedBody, + appendTranscriptionCompletedBody, + batchMetadataBody, +} from '../schemas'; export const internalRoutes = new Hono(); @@ -25,68 +31,32 @@ internalRoutes.use('*', async (c, next) => { // POST /transcription-completed — called by audio server on completion internalRoutes.post('/transcription-completed', async (c) => { - const body = await c.req.json<{ - memoId: string; - userId: string; - transcriptionResult?: { - transcript?: string; - utterances?: Array<{ offset: number; duration: number; text: string; speaker?: string }>; - speakers?: Record; - speakerMap?: Record; - languages?: string[]; - primary_language?: string; - duration?: number; - }; - route?: string; - success: boolean; - error?: string; - fallbackStage?: string; - }>(); - - if (!body.memoId || !body.userId) { - return c.json({ error: 'memoId and userId are required' }, 400); - } + const v = await validateBody(c, transcriptionCompletedBody); + if (!v.success) return v.response; + const body = v.data; try { await handleTranscriptionCompleted({ memoId: body.memoId, userId: body.userId, - ...(body.transcriptionResult ? { transcriptionResult: body.transcriptionResult } : {}), + ...(body.transcriptionResult ? { transcriptionResult: body.transcriptionResult as any } : {}), ...(body.route ? { route: body.route } : {}), success: body.success, ...(body.error ? { error: body.error } : {}), ...(body.fallbackStage ? { fallbackStage: body.fallbackStage } : {}), - }); + } as any); return c.json({ success: true, memoId: body.memoId }); } catch (err) { console.error('[internal] Transcription completed handler failed:', err); - return c.json({ error: 'Failed to process transcription callback' }, 500); + return c.json({ success: false, error: 'Failed to process transcription callback' }, 500); } }); // POST /append-transcription-completed — called by audio server for append flow internalRoutes.post('/append-transcription-completed', async (c) => { - const body = await c.req.json<{ - memoId: string; - userId: string; - recordingIndex: number; - transcriptionResult?: { - transcript?: string; - utterances?: Array<{ offset: number; duration: number; text: string; speaker?: string }>; - speakers?: Record; - speakerMap?: Record; - languages?: string[]; - primary_language?: string; - duration?: number; - }; - success: boolean; - error?: string; - route?: string; - }>(); - - if (!body.memoId || !body.userId) { - return c.json({ error: 'memoId and userId are required' }, 400); - } + const v = await validateBody(c, appendTranscriptionCompletedBody); + if (!v.success) return v.response; + const body = v.data; const supabase = createServiceClient(); const now = new Date().toISOString(); @@ -100,32 +70,36 @@ internalRoutes.post('/append-transcription-completed', async (c) => { .single(); if (fetchError || !memo) { - return c.json({ error: 'Memo not found' }, 404); + return c.json({ success: false, error: 'Memo not found' }, 404); } const source = (memo as { source: Record }).source ?? {}; const additionalRecordings = [...((source.additional_recordings as unknown[]) ?? [])]; - const recordingEntry = body.success && body.transcriptionResult - ? { - path: (additionalRecordings[body.recordingIndex] as { path?: string } | undefined)?.path ?? '', - transcript: body.transcriptionResult.transcript ?? '', - utterances: body.transcriptionResult.utterances ?? [], - speakers: body.transcriptionResult.speakers ?? {}, - speakerMap: body.transcriptionResult.speakerMap ?? {}, - languages: body.transcriptionResult.languages ?? [], - primary_language: body.transcriptionResult.primary_language ?? 'de', - status: 'completed', - timestamp: now, - updated_at: now, - route: body.route, - } - : { - ...(additionalRecordings[body.recordingIndex] as Record | undefined ?? {}), - status: 'error', - error: body.error ?? 'Transcription failed', - updated_at: now, - }; + const recordingEntry = + body.success && body.transcriptionResult + ? { + path: + (additionalRecordings[body.recordingIndex] as { path?: string } | undefined)?.path ?? + '', + transcript: body.transcriptionResult.transcript ?? '', + utterances: body.transcriptionResult.utterances ?? [], + speakers: body.transcriptionResult.speakers ?? {}, + speakerMap: body.transcriptionResult.speakerMap ?? {}, + languages: body.transcriptionResult.languages ?? [], + primary_language: body.transcriptionResult.primary_language ?? 'de', + status: 'completed', + timestamp: now, + updated_at: now, + route: body.route, + } + : { + ...((additionalRecordings[body.recordingIndex] as Record | undefined) ?? + {}), + status: 'error', + error: body.error ?? 'Transcription failed', + updated_at: now, + }; additionalRecordings[body.recordingIndex] = recordingEntry; @@ -139,7 +113,7 @@ internalRoutes.post('/append-transcription-completed', async (c) => { if (updateError) { console.error('[internal] Failed to update append transcription:', updateError); - return c.json({ error: 'Failed to update memo' }, 500); + return c.json({ success: false, error: 'Failed to update memo' }, 500); } return c.json({ success: true, memoId: body.memoId, recordingIndex: body.recordingIndex }); @@ -147,16 +121,9 @@ internalRoutes.post('/append-transcription-completed', async (c) => { // POST /batch-metadata — update memo with batch job metadata internalRoutes.post('/batch-metadata', async (c) => { - const body = await c.req.json<{ - memoId: string; - jobId: string; - batchTranscription?: boolean; - userId?: string; - }>(); - - if (!body.memoId || !body.jobId) { - return c.json({ error: 'memoId and jobId are required' }, 400); - } + const v = await validateBody(c, batchMetadataBody); + if (!v.success) return v.response; + const body = v.data; const supabase = createServiceClient(); @@ -167,7 +134,7 @@ internalRoutes.post('/batch-metadata', async (c) => { .single(); if (fetchError || !memo) { - return c.json({ error: 'Memo not found' }, 404); + return c.json({ success: false, error: 'Memo not found' }, 404); } const metadata = (memo as { metadata: Record }).metadata ?? {}; @@ -184,7 +151,7 @@ internalRoutes.post('/batch-metadata', async (c) => { .eq('id', body.memoId); if (updateError) { - return c.json({ error: 'Failed to update batch metadata' }, 500); + return c.json({ success: false, error: 'Failed to update batch metadata' }, 500); } return c.json({ success: true, memoId: body.memoId, jobId: body.jobId }); diff --git a/apps/memoro/apps/server/src/routes/invites.ts b/apps/memoro/apps/server/src/routes/invites.ts index 5657f1f13..ad221522a 100644 --- a/apps/memoro/apps/server/src/routes/invites.ts +++ b/apps/memoro/apps/server/src/routes/invites.ts @@ -5,6 +5,8 @@ import { Hono } from 'hono'; import type { AuthVariables } from '@manacore/shared-hono'; import { acceptInvite, declineInvite, getPendingInvites } from '../services/space'; +import { validateBody } from '../lib/validate'; +import { inviteActionBody } from '../schemas'; export const inviteRoutes = new Hono<{ Variables: AuthVariables }>(); @@ -13,47 +15,45 @@ inviteRoutes.get('/pending', async (c) => { const userId = c.get('userId') as string; try { const invites = await getPendingInvites(userId); - return c.json({ invites }); + return c.json({ success: true, invites }); } catch (err) { console.error('[invites] Get pending error:', err); - return c.json({ error: 'Failed to get pending invites' }, 500); + return c.json({ success: false, error: 'Failed to get pending invites' }, 500); } }); // POST /accept — accept an invite inviteRoutes.post('/accept', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ inviteId: string }>(); - - if (!body.inviteId) return c.json({ error: 'inviteId is required' }, 400); + const v = await validateBody(c, inviteActionBody); + if (!v.success) return v.response; try { - const result = await acceptInvite(body.inviteId, userId); - return c.json(result); + await acceptInvite(v.data.inviteId, userId); + return c.json({ success: true }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (msg.includes('not found') || msg.includes('already processed')) { - return c.json({ error: msg }, 404); + return c.json({ success: false, error: msg }, 404); } - return c.json({ error: 'Failed to accept invite' }, 500); + return c.json({ success: false, error: 'Failed to accept invite' }, 500); } }); // POST /decline — decline an invite inviteRoutes.post('/decline', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ inviteId: string }>(); - - if (!body.inviteId) return c.json({ error: 'inviteId is required' }, 400); + const v = await validateBody(c, inviteActionBody); + if (!v.success) return v.response; try { - const result = await declineInvite(body.inviteId, userId); - return c.json(result); + await declineInvite(v.data.inviteId, userId); + return c.json({ success: true }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (msg.includes('not found') || msg.includes('already processed')) { - return c.json({ error: msg }, 404); + return c.json({ success: false, error: msg }, 404); } - return c.json({ error: 'Failed to decline invite' }, 500); + return c.json({ success: false, error: 'Failed to decline invite' }, 500); } }); diff --git a/apps/memoro/apps/server/src/routes/meetings.ts b/apps/memoro/apps/server/src/routes/meetings.ts index 49bf16c81..2eacd5a46 100644 --- a/apps/memoro/apps/server/src/routes/meetings.ts +++ b/apps/memoro/apps/server/src/routes/meetings.ts @@ -6,8 +6,9 @@ import { Hono } from 'hono'; import type { AuthVariables } from '@manacore/shared-hono'; import { validateCredits, COSTS } from '../lib/credits'; +import { validateBody, validateQuery } from '../lib/validate'; +import { createBotBody, recordingToMemoBody, paginationQuery } from '../schemas'; import { - validateMeetingUrl, createBot, stopBot, getBots, @@ -24,20 +25,16 @@ const MINIMUM_RECORDING_CREDITS = 10; // POST /bots — create a meeting bot meetingRoutes.post('/bots', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ meeting_url?: string; space_id?: string }>(); - - if (!body.meeting_url || !validateMeetingUrl(body.meeting_url)) { - return c.json( - { error: 'Please provide a valid Teams, Google Meet, or Zoom meeting URL' }, - 400 - ); - } + const v = await validateBody(c, createBotBody); + if (!v.success) return v.response; + const { meeting_url, space_id } = v.data; // Validate minimum credits const creditCheck = await validateCredits(userId, 'meeting_recording', MINIMUM_RECORDING_CREDITS); if (!creditCheck.hasCredits) { return c.json( { + success: false, error: 'InsufficientCredits', message: `Not enough credits to start recording. Need at least ${MINIMUM_RECORDING_CREDITS} credits.`, details: { @@ -50,8 +47,10 @@ meetingRoutes.post('/bots', async (c) => { } try { - const webhookBaseUrl = (process.env.MEMORO_SERVER_URL ?? `http://localhost:${process.env.PORT ?? 3015}`).replace(/\/$/, ''); - const bot = await createBot(userId, body.meeting_url, webhookBaseUrl, body.space_id); + const webhookBaseUrl = ( + process.env.MEMORO_SERVER_URL ?? `http://localhost:${process.env.PORT ?? 3015}` + ).replace(/\/$/, ''); + const bot = await createBot(userId, meeting_url, webhookBaseUrl, space_id); return c.json({ success: true, bot, @@ -64,23 +63,24 @@ meetingRoutes.post('/bots', async (c) => { }); } catch (err) { const msg = err instanceof Error ? err.message : 'Failed to create meeting bot'; - return c.json({ error: msg }, 400); + return c.json({ success: false, error: msg }, 400); } }); -// GET /bots — list bots +// GET /bots — list bots (with pagination) meetingRoutes.get('/bots', async (c) => { const userId = c.get('userId') as string; const spaceId = c.req.query('space_id'); - const limit = Number(c.req.query('limit') ?? 50); - const offset = Number(c.req.query('offset') ?? 0); + const q = validateQuery(c, paginationQuery); + if (!q.success) return q.response; + const { limit, offset } = q.data; try { const bots = await getBots(userId, spaceId, limit, offset); - return c.json({ success: true, bots, total: bots.length }); + return c.json({ success: true, bots, total: bots.length, limit, offset }); } catch (err) { const msg = err instanceof Error ? err.message : 'Failed to fetch bots'; - return c.json({ error: msg }, 500); + return c.json({ success: false, error: msg }, 500); } }); @@ -90,7 +90,7 @@ meetingRoutes.get('/bots/:id', async (c) => { const botId = c.req.param('id'); const bot = await getBotById(botId, userId); - if (!bot) return c.json({ error: 'Bot not found' }, 404); + if (!bot) return c.json({ success: false, error: 'Bot not found' }, 404); return c.json({ success: true, bot }); }); @@ -106,23 +106,24 @@ meetingRoutes.post('/bots/:id/stop', async (c) => { } catch (err) { const msg = err instanceof Error ? err.message : 'Failed to stop bot'; const status = msg === 'Bot not found' ? 404 : 400; - return c.json({ error: msg }, status); + return c.json({ success: false, error: msg }, status); } }); -// GET /recordings — list recordings +// GET /recordings — list recordings (with pagination) meetingRoutes.get('/recordings', async (c) => { const userId = c.get('userId') as string; const spaceId = c.req.query('space_id'); - const limit = Number(c.req.query('limit') ?? 50); - const offset = Number(c.req.query('offset') ?? 0); + const q = validateQuery(c, paginationQuery); + if (!q.success) return q.response; + const { limit, offset } = q.data; try { const recordings = await getRecordings(userId, spaceId, limit, offset); - return c.json({ success: true, recordings, total: recordings.length }); + return c.json({ success: true, recordings, total: recordings.length, limit, offset }); } catch (err) { const msg = err instanceof Error ? err.message : 'Failed to fetch recordings'; - return c.json({ error: msg }, 500); + return c.json({ success: false, error: msg }, 500); } }); @@ -132,7 +133,7 @@ meetingRoutes.get('/recordings/:id', async (c) => { const recordingId = c.req.param('id'); const recording = await getRecordingById(recordingId, userId); - if (!recording) return c.json({ error: 'Recording not found' }, 404); + if (!recording) return c.json({ success: false, error: 'Recording not found' }, 404); return c.json({ success: true, recording }); }); @@ -141,14 +142,19 @@ meetingRoutes.get('/recordings/:id', async (c) => { meetingRoutes.post('/recordings/:id/to-memo', async (c) => { const userId = c.get('userId') as string; const recordingId = c.req.param('id'); - const body = await c.req.json<{ blueprintId?: string }>().catch(() => ({ blueprintId: undefined })); + const v = await validateBody(c, recordingToMemoBody).catch(() => ({ + success: true as const, + data: { blueprintId: undefined }, + })); + if (!v.success) return v.response; const authHeader = c.req.header('Authorization'); const recording = await getRecordingById(recordingId, userId); - if (!recording) return c.json({ error: 'Recording not found' }, 404); + if (!recording) return c.json({ success: false, error: 'Recording not found' }, 404); const filePath = recording.audio_url || recording.video_url; - if (!filePath) return c.json({ error: 'Recording has no audio or video file' }, 400); + if (!filePath) + return c.json({ success: false, error: 'Recording has no audio or video file' }, 400); const duration = recording.duration_seconds ?? 45; @@ -164,16 +170,22 @@ meetingRoutes.post('/recordings/:id/to-memo', async (c) => { filePath, duration, spaceId: recording.space_id, - blueprintId: body.blueprintId, + blueprintId: v.data.blueprintId, }), }); if (!response.ok) { - const errData = await response.json().catch(() => ({ message: 'Unknown error' })) as Record; - return c.json({ error: (errData.message as string) || 'Failed to process recording' }, 400); + const errData = (await response.json().catch(() => ({ message: 'Unknown error' }))) as Record< + string, + unknown + >; + return c.json( + { success: false, error: (errData.message as string) || 'Failed to process recording' }, + 400 + ); } - const result = await response.json() as Record; + const result = (await response.json()) as Record; return c.json({ success: true, memoId: result.memoId, @@ -184,6 +196,6 @@ meetingRoutes.post('/recordings/:id/to-memo', async (c) => { }); } catch (err) { const msg = err instanceof Error ? err.message : 'Failed to convert recording to memo'; - return c.json({ error: msg }, 500); + return c.json({ success: false, error: msg }, 500); } }); diff --git a/apps/memoro/apps/server/src/routes/memos.ts b/apps/memoro/apps/server/src/routes/memos.ts index 4fae5d894..c86eeca63 100644 --- a/apps/memoro/apps/server/src/routes/memos.ts +++ b/apps/memoro/apps/server/src/routes/memos.ts @@ -14,26 +14,17 @@ import { processHeadlineForMemo } from '../services/headline'; import { createServiceClient } from '../lib/supabase'; import { validateCredits, consumeCredits, COSTS } from '../lib/credits'; import { generateText } from '../lib/ai'; +import { validateBody } from '../lib/validate'; +import { createMemoBody, appendMemoBody, combineMemoBody, questionMemoBody } from '../schemas'; export const memoRoutes = new Hono<{ Variables: AuthVariables }>(); // POST / — create memo from uploaded file memoRoutes.post('/', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ - filePath: string; - duration: number; - spaceId?: string; - blueprintId?: string; - memoId?: string; - recordingStartedAt?: string; - location?: unknown; - mediaType?: string; - }>(); - - if (!body.filePath || body.duration == null) { - return c.json({ error: 'filePath and duration are required' }, 400); - } + const v = await validateBody(c, createMemoBody); + if (!v.success) return v.response; + const body = v.data; try { const result = await createMemoFromUploadedFile({ @@ -47,12 +38,12 @@ memoRoutes.post('/', async (c) => { ...(body.location !== undefined ? { location: body.location } : {}), ...(body.mediaType ? { mediaType: body.mediaType } : {}), }); - return c.json(result, 201); + return c.json({ success: true, ...result }, 201); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (msg.includes('Insufficient credits')) return c.json({ error: msg }, 402); + if (msg.includes('Insufficient credits')) return c.json({ success: false, error: msg }, 402); console.error('[memos] Create error:', err); - return c.json({ error: 'Failed to create memo' }, 500); + return c.json({ success: false, error: 'Failed to create memo' }, 500); } }); @@ -60,17 +51,9 @@ memoRoutes.post('/', async (c) => { memoRoutes.post('/:id/append', async (c) => { const userId = c.get('userId') as string; const memoId = c.req.param('id'); - const body = await c.req.json<{ - filePath: string; - duration: number; - recordingIndex?: number; - recordingLanguages?: string[]; - enableDiarization?: boolean; - }>(); - - if (!body.filePath || body.duration == null) { - return c.json({ error: 'filePath and duration are required' }, 400); - } + const v = await validateBody(c, appendMemoBody); + if (!v.success) return v.response; + const body = v.data; const supabase = createServiceClient(); @@ -83,14 +66,14 @@ memoRoutes.post('/:id/append', async (c) => { .single(); if (memoError || !memo) { - return c.json({ error: 'Memo not found or access denied' }, 404); + return c.json({ success: false, error: 'Memo not found or access denied' }, 404); } // Validate credits const cost = Math.max(Math.ceil((body.duration / 60) * COSTS.TRANSCRIPTION_PER_MINUTE), 2); const creditCheck = await validateCredits(userId, 'transcription', cost); if (!creditCheck.hasCredits) { - return c.json({ error: `Insufficient credits: need ${cost}` }, 402); + return c.json({ success: false, error: `Insufficient credits: need ${cost}` }, 402); } // Set processing status @@ -123,7 +106,9 @@ memoRoutes.post('/:id/append', async (c) => { duration: body.duration, recordingIndex, ...(body.recordingLanguages ? { recordingLanguages: body.recordingLanguages } : {}), - ...(body.enableDiarization !== undefined ? { enableDiarization: body.enableDiarization } : {}), + ...(body.enableDiarization !== undefined + ? { enableDiarization: body.enableDiarization } + : {}), isAppend: true, }).catch((err) => console.error(`[memos] Append transcription call failed: ${err}`)); }); @@ -144,7 +129,8 @@ memoRoutes.post('/:id/retry-transcription', async (c) => { .eq('user_id', userId) .single(); - if (error || !memo) return c.json({ error: 'Memo not found or access denied' }, 404); + if (error || !memo) + return c.json({ success: false, error: 'Memo not found or access denied' }, 404); const memoData = memo as { source: { audio_path?: string; duration?: number }; @@ -153,7 +139,8 @@ memoRoutes.post('/:id/retry-transcription', async (c) => { const filePath = memoData.source?.audio_path; const duration = memoData.source?.duration ?? 0; - if (!filePath) return c.json({ error: 'No audio file associated with this memo' }, 400); + if (!filePath) + return c.json({ success: false, error: 'No audio file associated with this memo' }, 400); await updateMemoProcessingStatus(memoId, 'transcription', 'pending'); @@ -180,29 +167,31 @@ memoRoutes.post('/:id/retry-headline', async (c) => { .eq('user_id', userId) .single(); - if (error || !memo) return c.json({ error: 'Memo not found or access denied' }, 404); + if (error || !memo) + return c.json({ success: false, error: 'Memo not found or access denied' }, 404); try { const result = await processHeadlineForMemo(memoId); - return c.json(result); + return c.json({ success: true, ...result }); } catch (err) { console.error(`[memos] Retry headline failed for ${memoId}:`, err); - return c.json({ error: 'Headline generation failed' }, 500); + return c.json({ success: false, error: 'Headline generation failed' }, 500); } }); // POST /combine — combine multiple memos with AI memoRoutes.post('/combine', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ memoIds: string[] }>(); - - if (!Array.isArray(body.memoIds) || body.memoIds.length < 2) { - return c.json({ error: 'At least 2 memoIds are required' }, 400); - } + const v = await validateBody(c, combineMemoBody); + if (!v.success) return v.response; + const { memoIds } = v.data; const creditCheck = await validateCredits(userId, 'memo_combine', COSTS.MEMO_COMBINE); if (!creditCheck.hasCredits) { - return c.json({ error: `Insufficient credits: need ${COSTS.MEMO_COMBINE}` }, 402); + return c.json( + { success: false, error: `Insufficient credits: need ${COSTS.MEMO_COMBINE}` }, + 402 + ); } const supabase = createServiceClient(); @@ -211,11 +200,11 @@ memoRoutes.post('/combine', async (c) => { const { data: memos, error: fetchError } = await supabase .from('memos') .select('id, title, source') - .in('id', body.memoIds) + .in('id', memoIds) .eq('user_id', userId); - if (fetchError || !memos || memos.length !== body.memoIds.length) { - return c.json({ error: 'One or more memos not found or access denied' }, 404); + if (fetchError || !memos || memos.length !== memoIds.length) { + return c.json({ success: false, error: 'One or more memos not found or access denied' }, 404); } // Extract transcripts @@ -248,7 +237,7 @@ CONTENT: `; const response = await generateText(prompt, { temperature: 0.7, maxTokens: 2048 }); await consumeCredits(userId, 'memo_combine', COSTS.MEMO_COMBINE, 'Combine memos', { - memoIds: body.memoIds, + memoIds, }); // Create combined memo @@ -270,7 +259,7 @@ CONTENT: `; source: { type: 'combined', transcript: content, - source_memo_ids: body.memoIds, + source_memo_ids: memoIds, }, metadata: { processing: { @@ -285,10 +274,10 @@ CONTENT: `; if (createError) throw createError; - return c.json({ memo: combinedMemo, headline, intro }); + return c.json({ success: true, memo: combinedMemo, headline, intro }); } catch (err) { console.error('[memos] Combine failed:', err); - return c.json({ error: 'Failed to combine memos' }, 500); + return c.json({ success: false, error: 'Failed to combine memos' }, 500); } }); @@ -296,15 +285,16 @@ CONTENT: `; memoRoutes.post('/:id/question', async (c) => { const userId = c.get('userId') as string; const memoId = c.req.param('id'); - const body = await c.req.json<{ question: string }>(); - - if (!body.question?.trim()) { - return c.json({ error: 'question is required' }, 400); - } + const v = await validateBody(c, questionMemoBody); + if (!v.success) return v.response; + const { question } = v.data; const creditCheck = await validateCredits(userId, 'question_memo', COSTS.QUESTION_MEMO); if (!creditCheck.hasCredits) { - return c.json({ error: `Insufficient credits: need ${COSTS.QUESTION_MEMO}` }, 402); + return c.json( + { success: false, error: `Insufficient credits: need ${COSTS.QUESTION_MEMO}` }, + 402 + ); } const supabase = createServiceClient(); @@ -316,7 +306,8 @@ memoRoutes.post('/:id/question', async (c) => { .eq('user_id', userId) .single(); - if (memoError || !memo) return c.json({ error: 'Memo not found or access denied' }, 404); + if (memoError || !memo) + return c.json({ success: false, error: 'Memo not found or access denied' }, 404); const memoData = memo as { title: string; source: Record }; const source = memoData.source ?? {}; @@ -333,14 +324,15 @@ memoRoutes.post('/:id/question', async (c) => { transcript = (source.transcript as string | undefined) ?? memoData.title; } - if (!transcript) return c.json({ error: 'No transcript available for this memo' }, 400); + if (!transcript) + return c.json({ success: false, error: 'No transcript available for this memo' }, 400); const prompt = `Du bist ein hilfreicher Assistent. Beantworte die folgende Frage basierend auf dem Transkript der Sprachaufnahme. Transkript: ${transcript} -Frage: ${body.question} +Frage: ${question} Antworte präzise und klar. Falls die Frage nicht aus dem Transkript beantwortet werden kann, sage das explizit.`; @@ -351,9 +343,9 @@ Antworte präzise und klar. Falls die Frage nicht aus dem Transkript beantwortet memoId, }); - return c.json({ answer, memoId, question: body.question }); + return c.json({ success: true, answer, memoId, question }); } catch (err) { console.error('[memos] Q&A failed:', err); - return c.json({ error: 'Failed to answer question' }, 500); + return c.json({ success: false, error: 'Failed to answer question' }, 500); } }); diff --git a/apps/memoro/apps/server/src/routes/settings.ts b/apps/memoro/apps/server/src/routes/settings.ts index 8b22ce593..b7b4cfc5e 100644 --- a/apps/memoro/apps/server/src/routes/settings.ts +++ b/apps/memoro/apps/server/src/routes/settings.ts @@ -7,6 +7,8 @@ import { Hono } from 'hono'; import type { AuthVariables } from '@manacore/shared-hono'; import { createServiceClient } from '../lib/supabase'; +import { validateBody } from '../lib/validate'; +import { updateMemoroSettingsBody, updateDataUsageBody, updateProfileBody } from '../schemas'; export const settingsRoutes = new Hono<{ Variables: AuthVariables }>(); @@ -23,10 +25,10 @@ settingsRoutes.get('/', async (c) => { if (error) { console.error('[settings] Get all error:', error); - return c.json({ error: 'Failed to get settings' }, 500); + return c.json({ success: false, error: 'Failed to get settings' }, 500); } - return c.json({ settings: profile ?? {} }); + return c.json({ success: true, settings: profile ?? {} }); }); // GET /memoro — get memoro-specific settings @@ -42,19 +44,22 @@ settingsRoutes.get('/memoro', async (c) => { if (error) { console.error('[settings] Get memoro error:', error); - return c.json({ error: 'Failed to get memoro settings' }, 500); + return c.json({ success: false, error: 'Failed to get memoro settings' }, 500); } - const appSettings = (profile as { app_settings?: Record } | null)?.app_settings ?? {}; + const appSettings = + (profile as { app_settings?: Record } | null)?.app_settings ?? {}; const memoroSettings = (appSettings.memoro as Record) ?? {}; - return c.json({ settings: memoroSettings }); + return c.json({ success: true, settings: memoroSettings }); }); // PATCH /memoro — update memoro settings settingsRoutes.patch('/memoro', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json>(); + const v = await validateBody(c, updateMemoroSettingsBody); + if (!v.success) return v.response; + const body = v.data; const supabase = createServiceClient(); // Get current settings @@ -65,7 +70,7 @@ settingsRoutes.patch('/memoro', async (c) => { .maybeSingle(); if (fetchError) { - return c.json({ error: 'Failed to fetch current settings' }, 500); + return c.json({ success: false, error: 'Failed to fetch current settings' }, 500); } const currentSettings = @@ -88,7 +93,7 @@ settingsRoutes.patch('/memoro', async (c) => { if (upsertError) { console.error('[settings] Update memoro error:', upsertError); - return c.json({ error: 'Failed to update memoro settings' }, 500); + return c.json({ success: false, error: 'Failed to update memoro settings' }, 500); } return c.json({ success: true, settings: { ...currentMemoro, ...body } }); @@ -97,7 +102,9 @@ settingsRoutes.patch('/memoro', async (c) => { // PATCH /memoro/data-usage — update data usage acceptance flag settingsRoutes.patch('/memoro/data-usage', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ accepted: boolean }>(); + const v = await validateBody(c, updateDataUsageBody); + if (!v.success) return v.response; + const { accepted } = v.data; const supabase = createServiceClient(); const { data: profile, error: fetchError } = await supabase @@ -107,7 +114,7 @@ settingsRoutes.patch('/memoro/data-usage', async (c) => { .maybeSingle(); if (fetchError) { - return c.json({ error: 'Failed to fetch current settings' }, 500); + return c.json({ success: false, error: 'Failed to fetch current settings' }, 500); } const currentSettings = @@ -118,8 +125,8 @@ settingsRoutes.patch('/memoro/data-usage', async (c) => { ...currentSettings, memoro: { ...currentMemoro, - dataUsageAcceptance: body.accepted, - dataUsageAcceptedAt: body.accepted ? new Date().toISOString() : null, + dataUsageAcceptance: accepted, + dataUsageAcceptedAt: accepted ? new Date().toISOString() : null, }, }; @@ -134,43 +141,35 @@ settingsRoutes.patch('/memoro/data-usage', async (c) => { if (upsertError) { console.error('[settings] Update data-usage error:', upsertError); - return c.json({ error: 'Failed to update data usage settings' }, 500); + return c.json({ success: false, error: 'Failed to update data usage settings' }, 500); } - return c.json({ success: true, dataUsageAcceptance: body.accepted }); + return c.json({ success: true, dataUsageAcceptance: accepted }); }); // PATCH /profile — update user profile fields settingsRoutes.patch('/profile', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ - display_name?: string; - avatar_url?: string; - bio?: string; - }>(); + const v = await validateBody(c, updateProfileBody); + if (!v.success) return v.response; + const body = v.data; - const allowedFields = ['display_name', 'avatar_url', 'bio'] as const; - const updateData: Record = { user_id: userId, updated_at: new Date().toISOString() }; + const updateData: Record = { + user_id: userId, + updated_at: new Date().toISOString(), + }; - for (const field of allowedFields) { - if (body[field] !== undefined) { - updateData[field] = body[field]; - } - } - - if (Object.keys(updateData).length <= 2) { - return c.json({ error: 'No valid fields provided' }, 400); - } + if (body.display_name !== undefined) updateData.display_name = body.display_name; + if (body.avatar_url !== undefined) updateData.avatar_url = body.avatar_url; + if (body.bio !== undefined) updateData.bio = body.bio; const supabase = createServiceClient(); - const { error } = await supabase - .from('profiles') - .upsert(updateData, { onConflict: 'user_id' }); + const { error } = await supabase.from('profiles').upsert(updateData, { onConflict: 'user_id' }); if (error) { console.error('[settings] Update profile error:', error); - return c.json({ error: 'Failed to update profile' }, 500); + return c.json({ success: false, error: 'Failed to update profile' }, 500); } return c.json({ success: true }); diff --git a/apps/memoro/apps/server/src/routes/spaces.ts b/apps/memoro/apps/server/src/routes/spaces.ts index f2c5776ae..4cdedb182 100644 --- a/apps/memoro/apps/server/src/routes/spaces.ts +++ b/apps/memoro/apps/server/src/routes/spaces.ts @@ -5,6 +5,8 @@ import { Hono } from 'hono'; import type { AuthVariables } from '@manacore/shared-hono'; import { createServiceClient } from '../lib/supabase'; +import { validateBody, validateQuery } from '../lib/validate'; +import { createSpaceBody, linkMemoBody, inviteBody, paginationQuery } from '../schemas'; import { getSpaces, createSpace, @@ -20,31 +22,37 @@ import { export const spaceRoutes = new Hono<{ Variables: AuthVariables }>(); -// GET / — list user's spaces +// GET / — list user's spaces (with pagination) spaceRoutes.get('/', async (c) => { const userId = c.get('userId') as string; + const q = validateQuery(c, paginationQuery); + if (!q.success) return q.response; + const { limit, offset } = q.data; + try { - const spaces = await getSpaces(userId); - return c.json({ spaces }); + const allSpaces = await getSpaces(userId); + const total = allSpaces.length; + const spaces = allSpaces.slice(offset, offset + limit); + return c.json({ success: true, spaces, total, limit, offset }); } catch (err) { console.error('[spaces] Get spaces error:', err); - return c.json({ error: 'Failed to get spaces' }, 500); + return c.json({ success: false, error: 'Failed to get spaces' }, 500); } }); // POST / — create space spaceRoutes.post('/', async (c) => { const userId = c.get('userId') as string; - const body = await c.req.json<{ name: string; description?: string }>(); - - if (!body.name?.trim()) return c.json({ error: 'name is required' }, 400); + const v = await validateBody(c, createSpaceBody); + if (!v.success) return v.response; + const { name, description } = v.data; try { - const space = await createSpace(userId, body.name, body.description); - return c.json({ space }, 201); + const space = await createSpace(userId, name, description); + return c.json({ success: true, space }, 201); } catch (err) { console.error('[spaces] Create error:', err); - return c.json({ error: 'Failed to create space' }, 500); + return c.json({ success: false, error: 'Failed to create space' }, 500); } }); @@ -55,14 +63,14 @@ spaceRoutes.get('/:id', async (c) => { try { const space = await getSpaceDetails(spaceId, userId); - return c.json({ space }); + return c.json({ success: true, space }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (msg.includes('Access denied') || msg.includes('not a member')) { - return c.json({ error: msg }, 403); + return c.json({ success: false, error: msg }, 403); } - if (msg.includes('not found')) return c.json({ error: msg }, 404); - return c.json({ error: 'Failed to get space details' }, 500); + if (msg.includes('not found')) return c.json({ success: false, error: msg }, 404); + return c.json({ success: false, error: 'Failed to get space details' }, 500); } }); @@ -72,13 +80,13 @@ spaceRoutes.delete('/:id', async (c) => { const spaceId = c.req.param('id'); try { - const result = await deleteSpace(spaceId, userId); - return c.json(result); + await deleteSpace(spaceId, userId); + return c.json({ success: true }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (msg.includes('owner')) return c.json({ error: msg }, 403); - if (msg.includes('not found')) return c.json({ error: msg }, 404); - return c.json({ error: 'Failed to delete space' }, 500); + if (msg.includes('owner')) return c.json({ success: false, error: msg }, 403); + if (msg.includes('not found')) return c.json({ success: false, error: msg }, 404); + return c.json({ success: false, error: 'Failed to delete space' }, 500); } }); @@ -88,13 +96,13 @@ spaceRoutes.post('/:id/leave', async (c) => { const spaceId = c.req.param('id'); try { - const result = await leaveSpace(spaceId, userId); - return c.json(result); + await leaveSpace(spaceId, userId); + return c.json({ success: true }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (msg.includes('not a member')) return c.json({ error: msg }, 403); - if (msg.includes('owner')) return c.json({ error: msg }, 400); - return c.json({ error: 'Failed to leave space' }, 500); + if (msg.includes('not a member')) return c.json({ success: false, error: msg }, 403); + if (msg.includes('owner')) return c.json({ success: false, error: msg }, 400); + return c.json({ success: false, error: 'Failed to leave space' }, 500); } }); @@ -102,20 +110,19 @@ spaceRoutes.post('/:id/leave', async (c) => { spaceRoutes.post('/:id/memos/link', async (c) => { const userId = c.get('userId') as string; const spaceId = c.req.param('id'); - const body = await c.req.json<{ memoId: string }>(); - - if (!body.memoId) return c.json({ error: 'memoId is required' }, 400); + const v = await validateBody(c, linkMemoBody); + if (!v.success) return v.response; try { - const result = await linkMemoToSpace(body.memoId, spaceId, userId); - return c.json(result); + await linkMemoToSpace(v.data.memoId, spaceId, userId); + return c.json({ success: true }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (msg.includes('access denied') || msg.includes('Not a member')) { - return c.json({ error: msg }, 403); + return c.json({ success: false, error: msg }, 403); } - if (msg.includes('not found')) return c.json({ error: msg }, 404); - return c.json({ error: 'Failed to link memo to space' }, 500); + if (msg.includes('not found')) return c.json({ success: false, error: msg }, 404); + return c.json({ success: false, error: 'Failed to link memo to space' }, 500); } }); @@ -123,33 +130,39 @@ spaceRoutes.post('/:id/memos/link', async (c) => { spaceRoutes.post('/:id/memos/unlink', async (c) => { const userId = c.get('userId') as string; const spaceId = c.req.param('id'); - const body = await c.req.json<{ memoId: string }>(); - - if (!body.memoId) return c.json({ error: 'memoId is required' }, 400); + const v = await validateBody(c, linkMemoBody); + if (!v.success) return v.response; try { - const result = await unlinkMemoFromSpace(body.memoId, spaceId, userId); - return c.json(result); + await unlinkMemoFromSpace(v.data.memoId, spaceId, userId); + return c.json({ success: true }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (msg.includes('access denied')) return c.json({ error: msg }, 403); - if (msg.includes('not found')) return c.json({ error: msg }, 404); - return c.json({ error: 'Failed to unlink memo from space' }, 500); + if (msg.includes('access denied')) return c.json({ success: false, error: msg }, 403); + if (msg.includes('not found')) return c.json({ success: false, error: msg }, 404); + return c.json({ success: false, error: 'Failed to unlink memo from space' }, 500); } }); -// GET /:id/memos — list space memos +// GET /:id/memos — list space memos (with pagination) spaceRoutes.get('/:id/memos', async (c) => { const userId = c.get('userId') as string; const spaceId = c.req.param('id'); + const q = validateQuery(c, paginationQuery); + if (!q.success) return q.response; + const { limit, offset } = q.data; try { const result = await getSpaceMemos(spaceId, userId); - return c.json(result); + const allMemos = result.memos ?? result; + const memos = Array.isArray(allMemos) ? allMemos : []; + const total = memos.length; + const paginated = memos.slice(offset, offset + limit); + return c.json({ success: true, memos: paginated, total, limit, offset }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (msg.includes('Not a member')) return c.json({ error: msg }, 403); - return c.json({ error: 'Failed to get space memos' }, 500); + if (msg.includes('Not a member')) return c.json({ success: false, error: msg }, 403); + return c.json({ success: false, error: 'Failed to get space memos' }, 500); } }); @@ -160,11 +173,11 @@ spaceRoutes.get('/:id/invites', async (c) => { try { const invites = await getSpaceInvites(spaceId, userId); - return c.json({ invites }); + return c.json({ success: true, invites }); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (msg.includes('Not a member')) return c.json({ error: msg }, 403); - return c.json({ error: 'Failed to get invites' }, 500); + if (msg.includes('Not a member')) return c.json({ success: false, error: msg }, 403); + return c.json({ success: false, error: 'Failed to get invites' }, 500); } }); @@ -172,25 +185,22 @@ spaceRoutes.get('/:id/invites', async (c) => { spaceRoutes.post('/:id/invite', async (c) => { const userId = c.get('userId') as string; const spaceId = c.req.param('id'); - const body = await c.req.json<{ email: string }>(); - - if (!body.email?.trim()) return c.json({ error: 'email is required' }, 400); + const v = await validateBody(c, inviteBody); + if (!v.success) return v.response; try { - const invite = await createInvite(spaceId, userId, body.email); - return c.json({ invite }, 201); + const invite = await createInvite(spaceId, userId, v.data.email); + return c.json({ success: true, invite }, 201); } catch (err) { const msg = err instanceof Error ? err.message : String(err); - if (msg.includes('Not a member')) return c.json({ error: msg }, 403); - return c.json({ error: 'Failed to create invite' }, 500); + if (msg.includes('Not a member')) return c.json({ success: false, error: msg }, 403); + return c.json({ success: false, error: 'Failed to create invite' }, 500); } }); // POST /invites/:inviteId/resend — resend invite spaceRoutes.post('/invites/:inviteId/resend', async (c) => { const inviteId = c.req.param('inviteId'); - // In a full implementation, this would resend the invite email via mana-notify - // For now, return success as the invite record already exists console.log(`[spaces] Resend invite ${inviteId} (email notification not implemented here)`); return c.json({ success: true, inviteId }); }); @@ -208,7 +218,7 @@ spaceRoutes.delete('/invites/:inviteId', async (c) => { .eq('id', inviteId) .single(); - if (error || !invite) return c.json({ error: 'Invite not found' }, 404); + if (error || !invite) return c.json({ success: false, error: 'Invite not found' }, 404); const inv = invite as { inviter_id: string; space_id: string }; @@ -223,14 +233,11 @@ spaceRoutes.delete('/invites/:inviteId', async (c) => { const isOwner = (spaceMember as { role: string } | null)?.role === 'owner'; if (inv.inviter_id !== userId && !isOwner) { - return c.json({ error: 'Not authorized to cancel this invite' }, 403); + return c.json({ success: false, error: 'Not authorized to cancel this invite' }, 403); } - const { error: deleteError } = await supabase - .from('space_invites') - .delete() - .eq('id', inviteId); + const { error: deleteError } = await supabase.from('space_invites').delete().eq('id', inviteId); - if (deleteError) return c.json({ error: 'Failed to cancel invite' }, 500); + if (deleteError) return c.json({ success: false, error: 'Failed to cancel invite' }, 500); return c.json({ success: true }); }); diff --git a/apps/memoro/apps/server/src/schemas.ts b/apps/memoro/apps/server/src/schemas.ts new file mode 100644 index 000000000..b01809ef5 --- /dev/null +++ b/apps/memoro/apps/server/src/schemas.ts @@ -0,0 +1,194 @@ +/** + * Zod validation schemas for all Memoro API endpoints. + */ + +import { z } from 'zod'; + +// ── Shared ──────────────────────────────────────────────────────────────────── + +export const paginationQuery = z.object({ + limit: z.coerce.number().int().min(1).max(100).default(50), + offset: z.coerce.number().int().min(0).default(0), +}); + +// ── Memos ───────────────────────────────────────────────────────────────────── + +export const createMemoBody = z.object({ + filePath: z.string().min(1, 'filePath is required'), + duration: z.number({ required_error: 'duration is required' }).min(0), + spaceId: z.string().uuid().optional(), + blueprintId: z.string().uuid().optional(), + memoId: z.string().uuid().optional(), + recordingStartedAt: z.string().optional(), + location: z.unknown().optional(), + mediaType: z.string().optional(), +}); + +export const appendMemoBody = z.object({ + filePath: z.string().min(1, 'filePath is required'), + duration: z.number({ required_error: 'duration is required' }).min(0), + recordingIndex: z.number().int().min(0).optional(), + recordingLanguages: z.array(z.string()).optional(), + enableDiarization: z.boolean().optional(), +}); + +export const combineMemoBody = z.object({ + memoIds: z.array(z.string().uuid()).min(2, 'At least 2 memoIds are required'), +}); + +export const questionMemoBody = z.object({ + question: z + .string() + .min(1, 'question is required') + .transform((v) => v.trim()), +}); + +// ── Spaces ──────────────────────────────────────────────────────────────────── + +export const createSpaceBody = z.object({ + name: z + .string() + .min(1, 'name is required') + .transform((v) => v.trim()), + description: z.string().optional(), +}); + +export const linkMemoBody = z.object({ + memoId: z.string().uuid('memoId must be a valid UUID'), +}); + +export const inviteBody = z.object({ + email: z + .string() + .email('Valid email is required') + .transform((v) => v.trim()), +}); + +// ── Invites ─────────────────────────────────────────────────────────────────── + +export const inviteActionBody = z.object({ + inviteId: z.string().uuid('inviteId is required'), +}); + +// ── Meetings ────────────────────────────────────────────────────────────────── + +const meetingUrlPattern = /^https:\/\/(teams\.microsoft\.com|meet\.google\.com|[\w-]+\.zoom\.us)\//; + +export const createBotBody = z.object({ + meeting_url: z + .string() + .regex(meetingUrlPattern, 'Please provide a valid Teams, Google Meet, or Zoom meeting URL'), + space_id: z.string().uuid().optional(), +}); + +export const recordingToMemoBody = z.object({ + blueprintId: z.string().uuid().optional(), +}); + +// ── Credits ─────────────────────────────────────────────────────────────────── + +export const checkCreditsBody = z.object({ + operation: z.string().min(1, 'operation is required'), + amount: z.number({ required_error: 'amount is required' }).min(0), +}); + +export const consumeCreditsBody = z.object({ + operation: z.string().min(1, 'operation is required'), + amount: z.number({ required_error: 'amount is required' }).min(0), + description: z.string().min(1, 'description is required'), + metadata: z.record(z.unknown()).optional(), +}); + +// ── Settings ────────────────────────────────────────────────────────────────── + +export const updateMemoroSettingsBody = z + .record(z.unknown()) + .refine((obj) => Object.keys(obj).length > 0, 'At least one setting is required'); + +export const updateDataUsageBody = z.object({ + accepted: z.boolean({ required_error: 'accepted is required' }), +}); + +export const updateProfileBody = z + .object({ + display_name: z.string().optional(), + avatar_url: z.string().url().optional(), + bio: z.string().max(500).optional(), + }) + .refine( + (obj) => Object.values(obj).some((v) => v !== undefined), + 'At least one field is required' + ); + +// ── Internal ────────────────────────────────────────────────────────────────── + +export const transcriptionCompletedBody = z.object({ + memoId: z.string().min(1, 'memoId is required'), + userId: z.string().min(1, 'userId is required'), + transcriptionResult: z + .object({ + transcript: z.string().optional(), + utterances: z + .array( + z.object({ + offset: z.number(), + duration: z.number(), + text: z.string(), + speaker: z.string().optional(), + }) + ) + .optional(), + speakers: z.record(z.unknown()).optional(), + speakerMap: z.record(z.unknown()).optional(), + languages: z.array(z.string()).optional(), + primary_language: z.string().optional(), + duration: z.number().optional(), + }) + .optional(), + route: z.string().optional(), + success: z.boolean(), + error: z.string().optional(), + fallbackStage: z.string().optional(), +}); + +export const appendTranscriptionCompletedBody = z.object({ + memoId: z.string().min(1, 'memoId is required'), + userId: z.string().min(1, 'userId is required'), + recordingIndex: z.number().int().min(0), + transcriptionResult: z + .object({ + transcript: z.string().optional(), + utterances: z + .array( + z.object({ + offset: z.number(), + duration: z.number(), + text: z.string(), + speaker: z.string().optional(), + }) + ) + .optional(), + speakers: z.record(z.unknown()).optional(), + speakerMap: z.record(z.unknown()).optional(), + languages: z.array(z.string()).optional(), + primary_language: z.string().optional(), + duration: z.number().optional(), + }) + .optional(), + success: z.boolean(), + error: z.string().optional(), + route: z.string().optional(), +}); + +export const batchMetadataBody = z.object({ + memoId: z.string().min(1, 'memoId is required'), + jobId: z.string().min(1, 'jobId is required'), + batchTranscription: z.boolean().optional(), + userId: z.string().optional(), +}); + +// ── Cleanup ─────────────────────────────────────────────────────────────────── + +export const manualCleanupBody = z.object({ + userIds: z.array(z.string().uuid()).optional(), +}); diff --git a/packages/shared-hono/src/rate-limit.ts b/packages/shared-hono/src/rate-limit.ts index 6796b52de..015284170 100644 --- a/packages/shared-hono/src/rate-limit.ts +++ b/packages/shared-hono/src/rate-limit.ts @@ -40,7 +40,7 @@ setInterval(() => { export function rateLimitMiddleware(options: RateLimitOptions = {}) { const { max = 100, windowMs = 60_000, keyFn } = options; - return async (c: Context, next: Next) => { + return async (c: Context, next: Next): Promise => { const key = keyFn ? keyFn(c) : c.req.header('x-forwarded-for')?.split(',')[0]?.trim() || diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e7f936959..d4a7c0e0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -250,14 +250,14 @@ importers: version: link:../../../../packages/shared-landing-ui astro: specifier: ^5.16.0 - version: 5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) + version: 5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) typescript: specifier: ^5.9.2 version: 5.9.3 devDependencies: '@astrojs/tailwind': specifier: ^6.0.2 - version: 6.0.2(astro@5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.3))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) + version: 6.0.2(astro@5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.3))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) '@tailwindcss/typography': specifier: ^0.5.18 version: 0.5.19(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.3)) @@ -266,13 +266,13 @@ importers: version: 20.19.25 eslint: specifier: ^9.0.0 - version: 9.39.1(jiti@1.21.7) + version: 9.39.1(jiti@2.6.1) eslint-config-prettier: specifier: ^9.1.0 - version: 9.1.2(eslint@9.39.1(jiti@1.21.7)) + version: 9.1.2(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-astro: specifier: ^1.0.0 - version: 1.5.0(eslint@9.39.1(jiti@1.21.7)) + version: 1.5.0(eslint@9.39.1(jiti@2.6.1)) prettier: specifier: ^3.6.2 version: 3.6.2 @@ -566,7 +566,7 @@ importers: version: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) expo-router: specifier: ~55.0.5 - version: 55.0.5(6d2zdlrrz5o6rsdio5yhgp3juy) + version: 55.0.5(fos732rmf7mlsrtwb6dv2k6f2i) expo-secure-store: specifier: ~55.0.8 version: 55.0.8(expo@55.0.5) @@ -857,7 +857,7 @@ importers: version: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) expo-router: specifier: ~55.0.5 - version: 55.0.5(bhhyukj6njqvlfm2o3z6hpwcja) + version: 55.0.5(shihlejigi2nkza7wltmngtxfm) expo-status-bar: specifier: ~55.0.4 version: 55.0.4(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) @@ -909,19 +909,19 @@ importers: version: 19.1.17 '@typescript-eslint/eslint-plugin': specifier: ^7.7.0 - version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) '@typescript-eslint/parser': specifier: ^7.7.0 - version: 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + version: 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) dotenv: specifier: ^16.4.7 version: 16.6.1 eslint: specifier: ^9.39.1 - version: 9.39.1(jiti@2.6.1) + version: 9.39.1(jiti@1.21.7) eslint-config-universe: specifier: ^12.0.1 - version: 12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)(typescript@5.3.3) + version: 12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)(typescript@5.3.3) prettier: specifier: ^3.2.5 version: 3.6.2 @@ -3202,6 +3202,9 @@ importers: uuid: specifier: ^11.0.0 version: 11.1.0 + zod: + specifier: ^3.24.0 + version: 3.25.76 devDependencies: '@types/node': specifier: ^20.0.0 @@ -26080,16 +26083,6 @@ snapshots: transitivePeerDependencies: - ts-node - '@astrojs/tailwind@6.0.2(astro@5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.3))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))': - dependencies: - astro: 5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) - autoprefixer: 10.4.22(postcss@8.5.8) - postcss: 8.5.8 - postcss-load-config: 4.0.2(postcss@8.5.8)(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) - tailwindcss: 3.4.18(tsx@4.21.0)(yaml@2.8.3) - transitivePeerDependencies: - - ts-node - '@astrojs/tailwind@6.0.2(astro@5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.18(tsx@4.21.0)(yaml@2.8.3))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3))': dependencies: astro: 5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3) @@ -28725,82 +28718,6 @@ snapshots: - supports-color - utf-8-validate - '@expo/cli@55.0.15(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(expo-router@55.0.5)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': - dependencies: - '@expo/code-signing-certificates': 0.0.6 - '@expo/config': 55.0.11(typescript@5.9.3) - '@expo/config-plugins': 55.0.7 - '@expo/devcert': 1.2.1 - '@expo/env': 2.1.1 - '@expo/image-utils': 0.8.12 - '@expo/json-file': 10.0.12 - '@expo/log-box': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@expo/metro': 54.2.0 - '@expo/metro-config': 55.0.9(expo@55.0.5)(typescript@5.9.3) - '@expo/osascript': 2.4.2 - '@expo/package-manager': 1.10.3 - '@expo/plist': 0.5.2 - '@expo/prebuild-config': 55.0.11(expo@55.0.5)(typescript@5.9.3) - '@expo/require-utils': 55.0.3(typescript@5.9.3) - '@expo/router-server': 55.0.9(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(expo-router@55.0.5)(expo-server@55.0.6)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@expo/schema-utils': 55.0.2 - '@expo/spawn-async': 1.7.2 - '@expo/ws-tunnel': 1.0.6 - '@expo/xcpretty': 4.4.1 - '@react-native/dev-middleware': 0.83.2 - accepts: 1.3.8 - arg: 5.0.2 - better-opn: 3.0.2 - bplist-creator: 0.1.0 - bplist-parser: 0.3.2 - chalk: 4.1.2 - ci-info: 3.9.0 - compression: 1.8.1 - connect: 3.7.0 - debug: 4.4.3 - dnssd-advertise: 1.1.3 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-server: 55.0.6 - fetch-nodeshim: 0.4.9 - getenv: 2.0.0 - glob: 13.0.0 - lan-network: 0.2.0 - multitars: 0.2.4 - node-forge: 1.3.3 - npm-package-arg: 11.0.3 - ora: 3.4.0 - picomatch: 4.0.3 - pretty-format: 29.7.0 - progress: 2.0.3 - prompts: 2.4.2 - resolve-from: 5.0.0 - semver: 7.7.3 - send: 0.19.1 - slugify: 1.6.6 - source-map-support: 0.5.21 - stacktrace-parser: 0.1.11 - structured-headers: 0.4.1 - terminal-link: 2.1.1 - toqr: 0.1.1 - wrap-ansi: 7.0.0 - ws: 8.18.3 - zod: 3.25.76 - optionalDependencies: - expo-router: 55.0.5(6d2zdlrrz5o6rsdio5yhgp3juy) - react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) - transitivePeerDependencies: - - '@expo/dom-webview' - - '@expo/metro-runtime' - - bufferutil - - expo-constants - - expo-font - - react - - react-dom - - react-server-dom-webpack - - supports-color - - typescript - - utf-8-validate - '@expo/cli@55.0.15(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4)(expo-router@55.0.5)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3)': dependencies: '@expo/code-signing-certificates': 0.0.6 @@ -28862,7 +28779,7 @@ snapshots: ws: 8.18.3 zod: 3.25.76 optionalDependencies: - expo-router: 55.0.5(bhhyukj6njqvlfm2o3z6hpwcja) + expo-router: 55.0.5(shihlejigi2nkza7wltmngtxfm) react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) transitivePeerDependencies: - '@expo/dom-webview' @@ -28953,6 +28870,82 @@ snapshots: - typescript - utf-8-validate + '@expo/cli@55.0.15(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4)(expo-router@55.0.5)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + dependencies: + '@expo/code-signing-certificates': 0.0.6 + '@expo/config': 55.0.11(typescript@5.9.3) + '@expo/config-plugins': 55.0.7 + '@expo/devcert': 1.2.1 + '@expo/env': 2.1.1 + '@expo/image-utils': 0.8.12 + '@expo/json-file': 10.0.12 + '@expo/log-box': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@expo/metro': 54.2.0 + '@expo/metro-config': 55.0.9(expo@55.0.5)(typescript@5.9.3) + '@expo/osascript': 2.4.2 + '@expo/package-manager': 1.10.3 + '@expo/plist': 0.5.2 + '@expo/prebuild-config': 55.0.11(expo@55.0.5)(typescript@5.9.3) + '@expo/require-utils': 55.0.3(typescript@5.9.3) + '@expo/router-server': 55.0.9(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4)(expo-router@55.0.5)(expo-server@55.0.6)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@expo/schema-utils': 55.0.2 + '@expo/spawn-async': 1.7.2 + '@expo/ws-tunnel': 1.0.6 + '@expo/xcpretty': 4.4.1 + '@react-native/dev-middleware': 0.83.2 + accepts: 1.3.8 + arg: 5.0.2 + better-opn: 3.0.2 + bplist-creator: 0.1.0 + bplist-parser: 0.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + compression: 1.8.1 + connect: 3.7.0 + debug: 4.4.3 + dnssd-advertise: 1.1.3 + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-server: 55.0.6 + fetch-nodeshim: 0.4.9 + getenv: 2.0.0 + glob: 13.0.0 + lan-network: 0.2.0 + multitars: 0.2.4 + node-forge: 1.3.3 + npm-package-arg: 11.0.3 + ora: 3.4.0 + picomatch: 4.0.3 + pretty-format: 29.7.0 + progress: 2.0.3 + prompts: 2.4.2 + resolve-from: 5.0.0 + semver: 7.7.3 + send: 0.19.1 + slugify: 1.6.6 + source-map-support: 0.5.21 + stacktrace-parser: 0.1.11 + structured-headers: 0.4.1 + terminal-link: 2.1.1 + toqr: 0.1.1 + wrap-ansi: 7.0.0 + ws: 8.18.3 + zod: 3.25.76 + optionalDependencies: + expo-router: 55.0.5(cdndhew7mqhhoslu6uoygxcgvm) + react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) + transitivePeerDependencies: + - '@expo/dom-webview' + - '@expo/metro-runtime' + - bufferutil + - expo-constants + - expo-font + - react + - react-dom + - react-server-dom-webpack + - supports-color + - typescript + - utf-8-validate + '@expo/cli@55.0.15(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4)(expo-router@55.0.5)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@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 @@ -29334,7 +29327,7 @@ snapshots: '@expo/dom-webview@55.0.3(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)': dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react: 19.2.0 react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) @@ -29488,7 +29481,7 @@ snapshots: dependencies: '@expo/dom-webview': 55.0.3(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) anser: 1.4.10 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react: 19.2.0 react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) stacktrace-parser: 0.1.11 @@ -29536,7 +29529,7 @@ snapshots: dependencies: '@expo/dom-webview': 55.0.3(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) anser: 1.4.10 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react: 19.2.0 react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) stacktrace-parser: 0.1.11 @@ -29703,7 +29696,7 @@ snapshots: postcss: 8.4.49 resolve-from: 5.0.0 optionalDependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) transitivePeerDependencies: - bufferutil - supports-color @@ -29750,7 +29743,7 @@ snapshots: dependencies: '@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) anser: 1.4.10 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) pretty-format: 29.7.0 react: 19.2.0 react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) @@ -29922,7 +29915,7 @@ snapshots: '@expo/json-file': 10.0.12 '@react-native/normalize-colors': 0.83.4 debug: 4.4.3 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@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.3 xml2js: 0.6.0 @@ -29983,21 +29976,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/router-server@55.0.9(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(expo-router@55.0.5)(expo-server@55.0.6)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': - dependencies: - debug: 4.4.3 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(typescript@5.9.3) - expo-font: 55.0.4(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - expo-server: 55.0.6 - react: 19.2.0 - optionalDependencies: - '@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - expo-router: 55.0.5(6d2zdlrrz5o6rsdio5yhgp3juy) - react-dom: 19.2.0(react@19.2.0) - transitivePeerDependencies: - - supports-color - '@expo/router-server@55.0.9(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4)(expo-router@55.0.5)(expo-server@55.0.6)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: debug: 4.4.3 @@ -30008,7 +29986,7 @@ snapshots: react: 19.2.0 optionalDependencies: '@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - expo-router: 55.0.5(bhhyukj6njqvlfm2o3z6hpwcja) + expo-router: 55.0.5(shihlejigi2nkza7wltmngtxfm) react-dom: 19.2.0(react@19.2.0) transitivePeerDependencies: - supports-color @@ -30546,7 +30524,7 @@ snapshots: - ts-node optional: true - '@jest/core@30.3.0(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3))': + '@jest/core@30.3.0(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3))': dependencies: '@jest/console': 30.3.0 '@jest/pattern': 30.0.1 @@ -30561,7 +30539,7 @@ snapshots: exit-x: 0.2.2 graceful-fs: 4.2.11 jest-changed-files: 30.3.0 - jest-config: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest-config: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)) jest-haste-map: 30.3.0 jest-message-util: 30.3.0 jest-regex-util: 30.0.1 @@ -35671,7 +35649,7 @@ snapshots: jest: 30.3.0(@types/node@20.19.25)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.9.3)) optional: true - '@testing-library/react-native@13.3.3(jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react-test-renderer@19.1.0(react@19.2.0))(react@19.2.0)': + '@testing-library/react-native@13.3.3(jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react-test-renderer@19.1.0(react@19.2.0))(react@19.2.0)': dependencies: jest-matcher-utils: 30.3.0 picocolors: 1.1.1 @@ -35681,7 +35659,7 @@ snapshots: react-test-renderer: 19.1.0(react@19.2.0) redent: 3.0.0 optionalDependencies: - jest: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)) optional: true '@testing-library/react-native@13.3.3(jest@30.3.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.3.3)))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react-test-renderer@19.1.0(react@19.2.0))(react@19.2.0)': @@ -36319,16 +36297,16 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) - '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/type-utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -36377,15 +36355,15 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/parser': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) '@typescript-eslint/scope-manager': 7.18.0 - '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) - '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/type-utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) '@typescript-eslint/visitor-keys': 7.18.0 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) graphemer: 1.4.0 ignore: 5.3.2 natural-compare: 1.4.0 @@ -36477,14 +36455,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': + '@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': dependencies: '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 6.21.0 debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: @@ -36516,14 +36494,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': + '@typescript-eslint/parser@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': dependencies: '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3) '@typescript-eslint/visitor-keys': 7.18.0 debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) optionalDependencies: typescript: 5.3.3 transitivePeerDependencies: @@ -36649,12 +36627,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': + '@typescript-eslint/type-utils@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) - '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/utils': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) ts-api-utils: 1.4.3(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 @@ -36685,12 +36663,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': + '@typescript-eslint/type-utils@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': dependencies: '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3) - '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/utils': 7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) debug: 4.4.3 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) ts-api-utils: 1.4.3(typescript@5.3.3) optionalDependencies: typescript: 5.3.3 @@ -36872,15 +36850,15 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': + '@typescript-eslint/utils@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) '@types/json-schema': 7.0.15 '@types/semver': 7.7.1 '@typescript-eslint/scope-manager': 6.21.0 '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.3.3) - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) semver: 7.7.3 transitivePeerDependencies: - supports-color @@ -36911,13 +36889,13 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@7.18.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3)': + '@typescript-eslint/utils@7.18.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) '@typescript-eslint/scope-manager': 7.18.0 '@typescript-eslint/types': 7.18.0 '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.3.3) - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) transitivePeerDependencies: - supports-color - typescript @@ -37212,11 +37190,11 @@ snapshots: - vite optional: true - '@vitest/browser@3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)': + '@vitest/browser@3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/mocker': 3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/mocker': 3.2.4(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)) '@vitest/utils': 3.2.4 magic-string: 0.30.21 sirv: 3.0.2 @@ -37346,15 +37324,6 @@ snapshots: optionalDependencies: vite: 6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/mocker@3.2.4(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))': - dependencies: - '@vitest/spy': 3.2.4 - estree-walker: 3.0.3 - magic-string: 0.30.21 - optionalDependencies: - vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3) - optional: true - '@vitest/mocker@4.0.14(vite@6.4.1(@types/node@22.19.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@vitest/spy': 4.0.14 @@ -37537,7 +37506,7 @@ snapshots: sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 2.0.0 - vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3) + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@29.0.1(@noble/hashes@2.0.1))(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3) optional: true '@vitest/ui@4.0.14(vitest@4.0.14)': @@ -38247,108 +38216,6 @@ snapshots: - terser - typescript - astro@5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@1.21.7)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3): - dependencies: - '@astrojs/compiler': 2.13.0 - '@astrojs/internal-helpers': 0.7.5 - '@astrojs/markdown-remark': 6.3.9 - '@astrojs/telemetry': 3.3.0 - '@capsizecss/unpack': 3.0.1 - '@oslojs/encoding': 1.1.0 - '@rollup/pluginutils': 5.3.0(rollup@4.53.3) - acorn: 8.15.0 - aria-query: 5.3.2 - axobject-query: 4.1.0 - boxen: 8.0.1 - ci-info: 4.3.1 - clsx: 2.1.1 - common-ancestor-path: 1.0.1 - cookie: 1.1.0 - cssesc: 3.0.0 - debug: 4.4.3 - deterministic-object-hash: 2.0.2 - devalue: 5.5.0 - diff: 5.2.0 - dlv: 1.1.3 - dset: 3.1.4 - es-module-lexer: 1.7.0 - esbuild: 0.25.12 - estree-walker: 3.0.3 - flattie: 1.1.1 - fontace: 0.3.1 - github-slugger: 2.0.0 - html-escaper: 3.0.3 - http-cache-semantics: 4.2.0 - import-meta-resolve: 4.2.0 - js-yaml: 4.1.1 - magic-string: 0.30.21 - magicast: 0.5.1 - mrmime: 2.0.1 - neotraverse: 0.6.18 - p-limit: 6.2.0 - p-queue: 8.1.1 - package-manager-detector: 1.5.0 - piccolore: 0.1.3 - picomatch: 4.0.3 - prompts: 2.4.2 - rehype: 13.0.2 - semver: 7.7.3 - shiki: 3.15.0 - smol-toml: 1.5.2 - svgo: 4.0.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tsconfck: 3.1.6(typescript@5.9.3) - ultrahtml: 1.6.0 - unifont: 0.6.0 - unist-util-visit: 5.0.0 - unstorage: 1.17.3(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(ioredis@5.9.2) - vfile: 6.0.3 - vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)) - xxhash-wasm: 1.1.0 - yargs-parser: 21.1.1 - yocto-spinner: 0.2.3 - zod: 3.25.76 - zod-to-json-schema: 3.25.0(zod@3.25.76) - zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) - optionalDependencies: - sharp: 0.34.5 - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@netlify/blobs' - - '@planetscale/database' - - '@types/node' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - aws4fetch - - db0 - - idb-keyval - - ioredis - - jiti - - less - - lightningcss - - rollup - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - tsx - - typescript - - uploadthing - - yaml - astro@5.16.0(@azure/storage-blob@12.31.0)(@netlify/blobs@10.7.4)(@types/node@20.19.25)(ioredis@5.9.2)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.53.3)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3): dependencies: '@astrojs/compiler': 2.13.0 @@ -39148,7 +39015,7 @@ snapshots: resolve-from: 5.0.0 optionalDependencies: '@babel/runtime': 7.29.2 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) transitivePeerDependencies: - '@babel/core' - supports-color @@ -41211,11 +41078,6 @@ snapshots: eslint: 9.39.1(jiti@2.6.1) semver: 7.7.3 - eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@1.21.7)): - dependencies: - eslint: 9.39.1(jiti@1.21.7) - semver: 7.7.3 - eslint-compat-utils@0.6.5(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -41280,14 +41142,14 @@ snapshots: dependencies: eslint: 8.57.1 + eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) - eslint-config-prettier@9.1.2(eslint@9.39.1(jiti@1.21.7)): - dependencies: - eslint: 9.39.1(jiti@1.21.7) - eslint-config-prettier@9.1.2(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -41312,17 +41174,17 @@ snapshots: - supports-color - typescript - eslint-config-universe@12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2)(typescript@5.3.3): + eslint-config-universe@12.1.0(@types/eslint@9.6.1)(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2)(typescript@5.3.3): dependencies: - '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) - '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) - eslint: 9.39.1(jiti@2.6.1) - eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-node: 11.1.0(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2) - eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@2.6.1)) - eslint-plugin-react-hooks: 4.6.2(eslint@9.39.1(jiti@2.6.1)) + '@typescript-eslint/eslint-plugin': 6.21.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + eslint: 9.39.1(jiti@1.21.7) + eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-node: 11.1.0(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-prettier: 5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2) + eslint-plugin-react: 7.37.5(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-react-hooks: 4.6.2(eslint@9.39.1(jiti@1.21.7)) optionalDependencies: prettier: 3.6.2 transitivePeerDependencies: @@ -41429,12 +41291,12 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@1.21.7)): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) - eslint: 9.39.1(jiti@2.6.1) + '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) + eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color @@ -41471,20 +41333,6 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-astro@1.5.0(eslint@9.39.1(jiti@1.21.7)): - dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@1.21.7)) - '@jridgewell/sourcemap-codec': 1.5.5 - '@typescript-eslint/types': 8.48.1 - astro-eslint-parser: 1.2.2 - eslint: 9.39.1(jiti@1.21.7) - eslint-compat-utils: 0.6.5(eslint@9.39.1(jiti@1.21.7)) - globals: 16.5.0 - postcss: 8.5.8 - postcss-selector-parser: 7.1.0 - transitivePeerDependencies: - - supports-color - eslint-plugin-astro@1.5.0(eslint@9.39.1(jiti@2.6.1)): dependencies: '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1(jiti@2.6.1)) @@ -41512,6 +41360,12 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 + eslint-plugin-es@3.0.1(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + eslint-utils: 2.1.0 + regexpp: 3.2.0 + eslint-plugin-es@3.0.1(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -41565,7 +41419,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint@9.39.1(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint@9.39.1(jiti@1.21.7)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -41574,9 +41428,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 9.39.1(jiti@2.6.1) + eslint: 9.39.1(jiti@1.21.7) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3))(eslint-import-resolver-node@0.3.9)(eslint@9.39.1(jiti@1.21.7)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -41588,7 +41442,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.3.3) + '@typescript-eslint/parser': 6.21.0(eslint@9.39.1(jiti@1.21.7))(typescript@5.3.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -41706,6 +41560,16 @@ snapshots: resolve: 1.22.11 semver: 6.3.1 + eslint-plugin-node@11.1.0(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + eslint-plugin-es: 3.0.1(eslint@9.39.1(jiti@1.21.7)) + eslint-utils: 2.1.0 + ignore: 5.3.2 + minimatch: 3.1.2 + resolve: 1.22.11 + semver: 6.3.1 + eslint-plugin-node@11.1.0(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -41736,6 +41600,16 @@ snapshots: '@types/eslint': 9.6.1 eslint-config-prettier: 8.10.2(eslint@8.57.1) + eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@1.21.7)))(eslint@9.39.1(jiti@1.21.7))(prettier@3.6.2): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + prettier: 3.6.2 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.11 + optionalDependencies: + '@types/eslint': 9.6.1 + eslint-config-prettier: 8.10.2(eslint@9.39.1(jiti@1.21.7)) + eslint-plugin-prettier@5.5.4(@types/eslint@9.6.1)(eslint-config-prettier@8.10.2(eslint@9.39.1(jiti@2.6.1)))(eslint@9.39.1(jiti@2.6.1))(prettier@3.6.2): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -41760,6 +41634,10 @@ snapshots: dependencies: eslint: 8.57.1 + eslint-plugin-react-hooks@4.6.2(eslint@9.39.1(jiti@1.21.7)): + dependencies: + eslint: 9.39.1(jiti@1.21.7) + eslint-plugin-react-hooks@4.6.2(eslint@9.39.1(jiti@2.6.1)): dependencies: eslint: 9.39.1(jiti@2.6.1) @@ -41790,6 +41668,28 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@1.21.7)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.39.1(jiti@1.21.7) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + eslint-plugin-react@7.37.5(eslint@9.39.1(jiti@2.6.1)): dependencies: array-includes: 3.1.9 @@ -42431,7 +42331,7 @@ snapshots: expo-dev-client@6.0.18(expo@55.0.5): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) expo-dev-launcher: 6.0.18(expo@55.0.5) expo-dev-menu: 7.0.17(expo@55.0.5) expo-dev-menu-interface: 2.0.0(expo@55.0.5) @@ -42472,7 +42372,7 @@ snapshots: expo-dev-launcher@6.0.18(expo@55.0.5): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) expo-dev-menu: 7.0.17(expo@55.0.5) expo-manifests: 1.0.9(expo@55.0.5) transitivePeerDependencies: @@ -42484,7 +42384,7 @@ snapshots: expo-dev-menu-interface@2.0.0(expo@55.0.5): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) expo-dev-menu-interface@55.0.1(expo@55.0.5): dependencies: @@ -42507,7 +42407,7 @@ snapshots: expo-dev-menu@7.0.17(expo@55.0.5): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) expo-dev-menu-interface: 2.0.0(expo@55.0.5) expo-device@55.0.10(expo@55.0.5): @@ -42538,7 +42438,7 @@ snapshots: expo-file-system@55.0.10(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0)): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) expo-file-system@55.0.10(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0)): @@ -42574,7 +42474,7 @@ snapshots: expo-font@55.0.4(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) fontfaceobserver: 2.3.0 react: 19.2.0 react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) @@ -42603,7 +42503,7 @@ snapshots: expo-glass-effect@55.0.8(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react: 19.2.0 react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) @@ -42626,11 +42526,11 @@ snapshots: expo-image-loader@55.0.0(expo@55.0.5): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) expo-image-picker@55.0.12(expo@55.0.5): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) expo-image-loader: 55.0.0(expo@55.0.5) expo-image@55.0.6(expo@54.0.25)(react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.2.4(react@19.1.0))(react@19.1.0))(react-native@0.81.4(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.1.0))(react@19.1.0): @@ -42645,7 +42545,7 @@ snapshots: expo-image@55.0.6(expo@55.0.5)(react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react: 19.2.0 react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) sf-symbols-typescript: 2.2.0 @@ -42687,7 +42587,7 @@ snapshots: expo-keep-awake@55.0.4(expo@55.0.5)(react@19.2.0): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react: 19.2.0 expo-keep-awake@55.0.4(expo@55.0.5)(react@19.2.4): @@ -42819,7 +42719,7 @@ snapshots: expo-manifests@1.0.9(expo@55.0.5): dependencies: '@expo/config': 12.0.10 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) expo-json-utils: 0.15.0 transitivePeerDependencies: - supports-color @@ -42965,106 +42865,6 @@ snapshots: - react-native - supports-color - expo-router@55.0.5(6d2zdlrrz5o6rsdio5yhgp3juy): - dependencies: - '@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@expo/schema-utils': 55.0.2 - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.2.0) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-navigation/bottom-tabs': 7.15.5(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@react-navigation/native': 7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@react-navigation/native-stack': 7.14.4(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - client-only: 0.0.1 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(typescript@5.9.3) - expo-glass-effect: 55.0.8(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - expo-image: 55.0.6(expo@55.0.5)(react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - expo-linking: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-server: 55.0.6 - expo-symbols: 55.0.5(expo-font@14.0.10)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - fast-deep-equal: 3.1.3 - invariant: 2.2.4 - nanoid: 3.3.11 - query-string: 7.1.3 - react: 19.2.0 - react-fast-compare: 3.2.2 - react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - react-native-safe-area-context: 5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - react-native-screens: 4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - semver: 7.6.3 - server-only: 0.0.1 - sf-symbols-typescript: 2.2.0 - shallowequal: 1.1.0 - use-latest-callback: 0.2.6(react@19.2.0) - vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - optionalDependencies: - '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-gesture-handler@2.28.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@testing-library/react-native': 13.3.3(jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react-test-renderer@19.1.0(react@19.2.0))(react@19.2.0) - react-dom: 19.2.0(react@19.2.0) - react-native-gesture-handler: 2.28.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - transitivePeerDependencies: - - '@react-native-masked-view/masked-view' - - '@types/react' - - '@types/react-dom' - - expo-font - - supports-color - - expo-router@55.0.5(bhhyukj6njqvlfm2o3z6hpwcja): - dependencies: - '@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@expo/schema-utils': 55.0.2 - '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.2.0) - '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@react-navigation/bottom-tabs': 7.15.5(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@react-navigation/native': 7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@react-navigation/native-stack': 7.14.4(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - client-only: 0.0.1 - debug: 4.4.3 - escape-string-regexp: 4.0.0 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) - expo-constants: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(typescript@5.3.3) - expo-glass-effect: 55.0.8(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - expo-image: 55.0.6(expo@55.0.5)(react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - expo-linking: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) - expo-server: 55.0.6 - expo-symbols: 55.0.5(expo-font@55.0.4)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - fast-deep-equal: 3.1.3 - invariant: 2.2.4 - nanoid: 3.3.11 - query-string: 7.1.3 - react: 19.2.0 - react-fast-compare: 3.2.2 - react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - react-native-safe-area-context: 5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - react-native-screens: 4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - semver: 7.6.3 - server-only: 0.0.1 - sf-symbols-typescript: 2.2.0 - shallowequal: 1.1.0 - use-latest-callback: 0.2.6(react@19.2.0) - vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - optionalDependencies: - '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-gesture-handler@2.28.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.7.4(@babel/core@7.28.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - '@testing-library/react-native': 13.3.3(jest@30.3.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.3.3)))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react-test-renderer@19.1.0(react@19.2.0))(react@19.2.0) - react-dom: 19.2.0(react@19.2.0) - react-native-gesture-handler: 2.28.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.7.4(@babel/core@7.28.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) - react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - transitivePeerDependencies: - - '@react-native-masked-view/masked-view' - - '@types/react' - - '@types/react-dom' - - expo-font - - supports-color - expo-router@55.0.5(cdndhew7mqhhoslu6uoygxcgvm): dependencies: '@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) @@ -43216,6 +43016,56 @@ snapshots: - supports-color optional: true + expo-router@55.0.5(fos732rmf7mlsrtwb6dv2k6f2i): + dependencies: + '@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@expo/schema-utils': 55.0.2 + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.2.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@react-navigation/bottom-tabs': 7.15.5(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@react-navigation/native': 7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@react-navigation/native-stack': 7.14.4(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + client-only: 0.0.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-constants: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(typescript@5.9.3) + expo-glass-effect: 55.0.8(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + expo-image: 55.0.6(expo@55.0.5)(react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + expo-linking: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo-server: 55.0.6 + expo-symbols: 55.0.5(expo-font@14.0.10)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + fast-deep-equal: 3.1.3 + invariant: 2.2.4 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 19.2.0 + react-fast-compare: 3.2.2 + react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) + react-native-is-edge-to-edge: 1.2.1(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + react-native-safe-area-context: 5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + react-native-screens: 4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + semver: 7.6.3 + server-only: 0.0.1 + sf-symbols-typescript: 2.2.0 + shallowequal: 1.1.0 + use-latest-callback: 0.2.6(react@19.2.0) + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + optionalDependencies: + '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-gesture-handler@2.28.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@testing-library/react-native': 13.3.3(jest@30.3.0(@types/node@24.10.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@24.10.1)(typescript@5.9.3)))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react-test-renderer@19.1.0(react@19.2.0))(react@19.2.0) + react-dom: 19.2.0(react@19.2.0) + react-native-gesture-handler: 2.28.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.5.1(@babel/core@7.28.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + - '@types/react' + - '@types/react-dom' + - expo-font + - supports-color + expo-router@55.0.5(ikidcy22c24hpqhht3cnsne4p4): dependencies: '@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.84.1(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) @@ -43417,9 +43267,59 @@ snapshots: - expo-font - supports-color + expo-router@55.0.5(shihlejigi2nkza7wltmngtxfm): + dependencies: + '@expo/log-box': 55.0.8(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@expo/metro-runtime': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@expo/schema-utils': 55.0.2 + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.17)(react@19.2.0) + '@radix-ui/react-tabs': 1.1.13(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@react-navigation/bottom-tabs': 7.15.5(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@react-navigation/native': 7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@react-navigation/native-stack': 7.14.4(@react-navigation/native@7.1.33(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + client-only: 0.0.1 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) + expo-constants: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(typescript@5.3.3) + expo-glass-effect: 55.0.8(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + expo-image: 55.0.6(expo@55.0.5)(react-native-web@0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + expo-linking: 55.0.7(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) + expo-server: 55.0.6 + expo-symbols: 55.0.5(expo-font@55.0.4)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + fast-deep-equal: 3.1.3 + invariant: 2.2.4 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 19.2.0 + react-fast-compare: 3.2.2 + react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) + react-native-is-edge-to-edge: 1.2.1(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + react-native-safe-area-context: 5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + react-native-screens: 4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + semver: 7.6.3 + server-only: 0.0.1 + sf-symbols-typescript: 2.2.0 + shallowequal: 1.1.0 + use-latest-callback: 0.2.6(react@19.2.0) + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.1.17))(@types/react@19.1.17)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + optionalDependencies: + '@react-navigation/drawer': 7.7.4(@react-navigation/native@7.1.21(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-gesture-handler@2.28.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-reanimated@4.1.5(@babel/core@7.28.5)(react-native-worklets@0.7.4(@babel/core@7.28.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native-screens@4.16.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@testing-library/react-native': 13.3.3(jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react-test-renderer@19.1.0(react@19.2.0))(react@19.2.0) + react-dom: 19.2.0(react@19.2.0) + react-native-gesture-handler: 2.28.0(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + react-native-reanimated: 4.1.5(@babel/core@7.28.5)(react-native-worklets@0.7.4(@babel/core@7.28.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + - '@types/react' + - '@types/react-dom' + - expo-font + - supports-color + expo-secure-store@55.0.8(expo@55.0.5): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) expo-server@1.0.4: {} @@ -43550,7 +43450,7 @@ snapshots: dependencies: '@react-native/normalize-colors': 0.83.2 debug: 4.4.3 - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) optionalDependencies: react-native-web: 0.21.2(encoding@0.1.13)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -43580,7 +43480,7 @@ snapshots: expo-updates-interface@2.0.0(expo@55.0.5): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) expo-updates-interface@55.1.3(expo@55.0.5): dependencies: @@ -43615,7 +43515,7 @@ snapshots: expo-web-browser@55.0.9(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0)): dependencies: - expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.3.3) react-native: 0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0) expo-web-browser@55.0.9(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.2.14)(react@19.2.0)): @@ -43781,7 +43681,7 @@ snapshots: expo@55.0.5(@babel/core@7.28.5)(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-router@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native-webview@13.12.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 - '@expo/cli': 55.0.15(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(expo-router@55.0.5)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@expo/cli': 55.0.15(@expo/dom-webview@55.0.3)(@expo/metro-runtime@55.0.7)(expo-constants@55.0.7)(expo-font@55.0.4)(expo-router@55.0.5)(expo@55.0.5)(react-dom@19.2.0(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) '@expo/config': 55.0.11(typescript@5.9.3) '@expo/config-plugins': 55.0.7 '@expo/devtools': 55.0.2(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) @@ -43790,7 +43690,7 @@ snapshots: '@expo/log-box': 55.0.7(@expo/dom-webview@55.0.3)(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) '@expo/metro': 54.2.0 '@expo/metro-config': 55.0.9(expo@55.0.5)(typescript@5.9.3) - '@expo/vector-icons': 15.0.3(expo-font@55.0.4(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0))(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) + '@expo/vector-icons': 15.0.3(expo-font@55.0.4)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0) '@ungap/structured-clone': 1.3.0 babel-preset-expo: 55.0.10(@babel/core@7.28.5)(@babel/runtime@7.29.2)(expo@55.0.5)(react-refresh@0.14.2) expo-asset: 55.0.8(expo@55.0.5)(react-native@0.83.2(@babel/core@7.28.5)(@types/react@19.1.17)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) @@ -45600,15 +45500,15 @@ snapshots: - ts-node optional: true - jest-cli@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): + jest-cli@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)): dependencies: - '@jest/core': 30.3.0(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + '@jest/core': 30.3.0(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)) '@jest/test-result': 30.3.0 '@jest/types': 30.3.0 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest-config: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)) jest-util: 30.3.0 jest-validate: 30.3.0 yargs: 17.7.2 @@ -45810,7 +45710,7 @@ snapshots: - supports-color optional: true - jest-config@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): + jest-config@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)): dependencies: '@babel/core': 7.28.5 '@jest/get-type': 30.1.0 @@ -45838,7 +45738,7 @@ snapshots: optionalDependencies: '@types/node': 22.19.1 esbuild-register: 3.6.0(esbuild@0.27.4) - ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.9.3) + ts-node: 10.9.2(@types/node@22.19.1)(typescript@5.3.3) transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -46530,12 +46430,12 @@ snapshots: - ts-node optional: true - jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)): + jest@30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)): dependencies: - '@jest/core': 30.3.0(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + '@jest/core': 30.3.0(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)) '@jest/types': 30.3.0 import-local: 3.2.0 - jest-cli: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3)) + jest-cli: 30.3.0(@types/node@22.19.1)(esbuild-register@3.6.0(esbuild@0.27.4))(ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3)) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -52512,6 +52412,25 @@ snapshots: yn: 3.1.1 optional: true + ts-node@10.9.2(@types/node@22.19.1)(typescript@5.3.3): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.12 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.19.1 + acorn: 8.15.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.3.3 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + optional: true + ts-node@10.9.2(@types/node@22.19.1)(typescript@5.9.3): dependencies: '@cspotcode/source-map-support': 0.8.1 @@ -53428,23 +53347,6 @@ snapshots: lightningcss: 1.30.2 terser: 5.44.1 - vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3): - dependencies: - esbuild: 0.25.12 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.53.3 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 20.19.25 - fsevents: 2.3.3 - jiti: 1.21.7 - lightningcss: 1.30.2 - terser: 5.44.1 - tsx: 4.21.0 - yaml: 2.8.3 - vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: esbuild: 0.25.12 @@ -53534,10 +53436,6 @@ snapshots: optionalDependencies: vite: 5.4.21(@types/node@22.19.1)(lightningcss@1.30.2)(terser@5.44.1) - vitefu@1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)): - optionalDependencies: - vite: 6.4.1(@types/node@20.19.25)(jiti@1.21.7)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3) - vitefu@1.1.1(vite@6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: vite: 6.4.1(@types/node@20.19.25)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3) @@ -53819,7 +53717,7 @@ snapshots: optionalDependencies: '@types/debug': 4.1.12 '@types/node': 24.10.1 - '@vitest/browser': 3.2.4(playwright@1.57.0)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4) + '@vitest/browser': 3.2.4(playwright@1.57.0)(vite@6.4.1(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.3))(vitest@3.2.4) '@vitest/ui': 3.2.4(vitest@3.2.4) jsdom: 29.0.1(@noble/hashes@2.0.1) transitivePeerDependencies: