From 2a6c4a34f2ab4079b312023d711667a13914f0da Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 01:38:17 -0600 Subject: [PATCH 01/21] feat(trails): add trail search micro frontend acquisition surface MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduces apps/trails — a standalone Next.js + Cloudflare Worker app deployed to trails.packratai.com. Visitors can explore nearby trails on a public Leaflet/Overpass map without auth. Search is gated behind account creation, converting anonymous visitors into PackRat users before nudging them toward the native app. Key pieces: - CF Worker hybrid: serves Next.js static export + proxies /api/* to PackRat API with Bearer token pass-through, CORS, rate limiting, and edge caching on trail detail responses - Public map: Overpass API queries via @packrat/overpass, no auth required, geolocation with US center fallback - Auth gate: tabbed register/login/forgot-password dialog; full email OTP verification flow using existing /api/auth/* endpoints - Web token storage: localStorage with resilientTokenStorage pattern (JSON-parse guard for atomWithStorage compat) - Authenticated search: authedFetch with automatic 401 refresh + retry - Download CTA: dismissable sticky banner with mobile UA detection for App Store / Google Play deep links 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/trails/app/globals.css | 31 +++ apps/trails/app/layout.tsx | 34 ++++ apps/trails/app/page.tsx | 5 + apps/trails/components/AuthGate.tsx | 265 +++++++++++++++++++++++++ apps/trails/components/DownloadCTA.tsx | 83 ++++++++ apps/trails/components/SearchBar.tsx | 51 +++++ apps/trails/components/TrailCard.tsx | 73 +++++++ apps/trails/components/TrailMap.tsx | 102 ++++++++++ apps/trails/components/TrailsPage.tsx | 241 ++++++++++++++++++++++ apps/trails/components/VerifyEmail.tsx | 103 ++++++++++ apps/trails/components/ui/sonner.tsx | 7 + apps/trails/lib/apiFetch.ts | 47 +++++ apps/trails/lib/auth.ts | 140 +++++++++++++ apps/trails/lib/geolocation.ts | 18 ++ apps/trails/lib/overpass.ts | 32 +++ apps/trails/lib/trailSearch.ts | 78 ++++++++ apps/trails/lib/useAuth.tsx | 139 +++++++++++++ apps/trails/next.config.mjs | 16 ++ apps/trails/package.json | 47 +++++ apps/trails/postcss.config.mjs | 9 + apps/trails/tailwind.config.ts | 16 ++ apps/trails/tsconfig.json | 32 +++ apps/trails/worker/index.ts | 86 ++++++++ apps/trails/wrangler.jsonc | 28 +++ bun.lock | 40 ++++ package.json | 1 + tsconfig.json | 3 + 27 files changed, 1727 insertions(+) create mode 100644 apps/trails/app/globals.css create mode 100644 apps/trails/app/layout.tsx create mode 100644 apps/trails/app/page.tsx create mode 100644 apps/trails/components/AuthGate.tsx create mode 100644 apps/trails/components/DownloadCTA.tsx create mode 100644 apps/trails/components/SearchBar.tsx create mode 100644 apps/trails/components/TrailCard.tsx create mode 100644 apps/trails/components/TrailMap.tsx create mode 100644 apps/trails/components/TrailsPage.tsx create mode 100644 apps/trails/components/VerifyEmail.tsx create mode 100644 apps/trails/components/ui/sonner.tsx create mode 100644 apps/trails/lib/apiFetch.ts create mode 100644 apps/trails/lib/auth.ts create mode 100644 apps/trails/lib/geolocation.ts create mode 100644 apps/trails/lib/overpass.ts create mode 100644 apps/trails/lib/trailSearch.ts create mode 100644 apps/trails/lib/useAuth.tsx create mode 100644 apps/trails/next.config.mjs create mode 100644 apps/trails/package.json create mode 100644 apps/trails/postcss.config.mjs create mode 100644 apps/trails/tailwind.config.ts create mode 100644 apps/trails/tsconfig.json create mode 100644 apps/trails/worker/index.ts create mode 100644 apps/trails/wrangler.jsonc diff --git a/apps/trails/app/globals.css b/apps/trails/app/globals.css new file mode 100644 index 0000000000..ac47380540 --- /dev/null +++ b/apps/trails/app/globals.css @@ -0,0 +1,31 @@ +@import "../../../packages/web-ui/src/styles/globals.css"; +@import "leaflet/dist/leaflet.css"; + +@layer base { + html { + scroll-behavior: smooth; + } + + body { + @apply bg-background text-foreground; + } +} + +/* Leaflet map container */ +.trail-map { + @apply h-full w-full rounded-lg; +} + +/* Ensure Leaflet z-index plays nicely with modals */ +.leaflet-pane { + z-index: 10; +} + +.leaflet-top, +.leaflet-bottom { + z-index: 10; +} + +.leaflet-control { + z-index: 10; +} diff --git a/apps/trails/app/layout.tsx b/apps/trails/app/layout.tsx new file mode 100644 index 0000000000..85e6118819 --- /dev/null +++ b/apps/trails/app/layout.tsx @@ -0,0 +1,34 @@ +import { cn } from '@packrat/web-ui/lib/utils'; +import type { Metadata } from 'next'; +import { Inter } from 'next/font/google'; +import type React from 'react'; +import { Toaster } from 'trails-app/components/ui/sonner'; +import { AuthProvider } from 'trails-app/lib/useAuth'; +import './globals.css'; + +const inter = Inter({ subsets: ['latin'], variable: '--font-sans' }); + +export const metadata: Metadata = { + title: 'Trail Search — PackRat', + description: 'Discover hiking, cycling, and outdoor trails near you. Powered by PackRat.', + keywords: ['trail search', 'hiking trails', 'outdoor trails', 'trail finder', 'PackRat'], + openGraph: { + type: 'website', + title: 'Trail Search — PackRat', + description: 'Discover hiking, cycling, and outdoor trails near you.', + siteName: 'PackRat', + }, +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + + + {children} + + + + + ); +} diff --git a/apps/trails/app/page.tsx b/apps/trails/app/page.tsx new file mode 100644 index 0000000000..00a636b826 --- /dev/null +++ b/apps/trails/app/page.tsx @@ -0,0 +1,5 @@ +import { TrailsPage } from 'trails-app/components/TrailsPage'; + +export default function Page() { + return ; +} diff --git a/apps/trails/components/AuthGate.tsx b/apps/trails/components/AuthGate.tsx new file mode 100644 index 0000000000..6e3ae8c7a1 --- /dev/null +++ b/apps/trails/components/AuthGate.tsx @@ -0,0 +1,265 @@ +'use client'; + +import { Button } from '@packrat/web-ui'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from '@packrat/web-ui/components/dialog'; +import { Input } from '@packrat/web-ui/components/input'; +import { Label } from '@packrat/web-ui/components/label'; +import { Tabs, TabsContent, TabsList, TabsTrigger } from '@packrat/web-ui/components/tabs'; +import { Loader2 } from 'lucide-react'; +import { useState } from 'react'; +import { toast } from 'sonner'; +import { VerifyEmail } from 'trails-app/components/VerifyEmail'; +import { apiForgotPassword } from 'trails-app/lib/auth'; +import { useAuth } from 'trails-app/lib/useAuth'; + +type Tab = 'register' | 'login' | 'forgot'; + +export function AuthGate() { + const { authGateOpen, closeAuthGate, register, login, pendingEmail } = useAuth(); + const [tab, setTab] = useState('register'); + const [loading, setLoading] = useState(false); + + // Register form + const [regEmail, setRegEmail] = useState(''); + const [regPassword, setRegPassword] = useState(''); + const [regUsername, setRegUsername] = useState(''); + + // Login form + const [loginEmail, setLoginEmail] = useState(''); + const [loginPassword, setLoginPassword] = useState(''); + + // Forgot form + const [forgotEmail, setForgotEmail] = useState(''); + const [forgotSent, setForgotSent] = useState(false); + + async function handleRegister(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + try { + await register(regEmail, { password: regPassword, username: regUsername }); + } catch (err) { + const msg = err instanceof Error ? err.message : 'Registration failed'; + if (msg.toLowerCase().includes('already') || msg.toLowerCase().includes('exists')) { + toast.error('Account already exists.', { + action: { label: 'Log in', onClick: () => setTab('login') }, + }); + } else { + toast.error(msg); + } + } finally { + setLoading(false); + } + } + + async function handleLogin(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + try { + await login(loginEmail, loginPassword); + toast.success('Logged in! Search unlocked.'); + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Login failed. Check your credentials.'); + } finally { + setLoading(false); + } + } + + async function handleForgot(e: React.FormEvent) { + e.preventDefault(); + setLoading(true); + try { + await apiForgotPassword(forgotEmail); + setForgotSent(true); + } catch { + toast.error('Could not send reset email. Try again.'); + } finally { + setLoading(false); + } + } + + return ( + !open && closeAuthGate()}> + + + + {pendingEmail ? 'Verify your email' : 'Search trails on PackRat'} + + + {pendingEmail + ? 'Enter the 6-digit code we sent you to unlock search.' + : 'Create a free account to search trails by name or location.'} + + + + {pendingEmail ? ( + + ) : ( + setTab(v as Tab)}> + + Create account + Log in + + + +
+
+ + setRegUsername(e.target.value)} + required + autoComplete="username" + /> +
+
+ + setRegEmail(e.target.value)} + required + autoComplete="email" + /> +
+
+ + setRegPassword(e.target.value)} + required + minLength={8} + autoComplete="new-password" + /> +
+ +

+ By creating an account you agree to our{' '} + + Terms + {' '} + and{' '} + + Privacy Policy + + . +

+
+
+ + +
+
+ + setLoginEmail(e.target.value)} + required + autoComplete="email" + /> +
+
+
+ + +
+ setLoginPassword(e.target.value)} + required + autoComplete="current-password" + /> +
+ +
+
+ + + {forgotSent ? ( +
+

Check your inbox

+

+ We sent a password reset link to{' '} + {forgotEmail}. +

+ +
+ ) : ( +
+

+ Enter your email and we'll send you a link to reset your password. +

+
+ + setForgotEmail(e.target.value)} + required + autoComplete="email" + /> +
+ + +
+ )} +
+
+ )} +
+
+ ); +} diff --git a/apps/trails/components/DownloadCTA.tsx b/apps/trails/components/DownloadCTA.tsx new file mode 100644 index 0000000000..3b8555c5a6 --- /dev/null +++ b/apps/trails/components/DownloadCTA.tsx @@ -0,0 +1,83 @@ +'use client'; + +import { Button } from '@packrat/web-ui'; +import { X } from 'lucide-react'; +import { useEffect, useState } from 'react'; + +const DISMISSED_KEY = 'download_cta_dismissed'; +const IOS_RE = /iphone|ipad|ipod/; +const ANDROID_RE = /android/; + +function getStoreLinks() { + if (typeof navigator === 'undefined') return { ios: false, android: false }; + const ua = navigator.userAgent.toLowerCase(); + return { + ios: IOS_RE.test(ua), + android: ANDROID_RE.test(ua), + }; +} + +export function DownloadCTA() { + const [visible, setVisible] = useState(false); + const [platform, setPlatform] = useState<'ios' | 'android' | 'both'>('both'); + + useEffect(() => { + if (sessionStorage.getItem(DISMISSED_KEY)) return; + const { ios, android } = getStoreLinks(); + if (ios) setPlatform('ios'); + else if (android) setPlatform('android'); + setVisible(true); + }, []); + + function dismiss() { + sessionStorage.setItem(DISMISSED_KEY, '1'); + setVisible(false); + } + + if (!visible) return null; + + return ( +
+
+
+

Get more with PackRat

+

+ Plan trips, track gear, and explore trails — all in one app. +

+
+
+ {(platform === 'ios' || platform === 'both') && ( + + )} + {(platform === 'android' || platform === 'both') && ( + + )} + +
+
+
+ ); +} diff --git a/apps/trails/components/SearchBar.tsx b/apps/trails/components/SearchBar.tsx new file mode 100644 index 0000000000..f2a888711f --- /dev/null +++ b/apps/trails/components/SearchBar.tsx @@ -0,0 +1,51 @@ +'use client'; + +import { Input } from '@packrat/web-ui/components/input'; +import { Loader2, Search } from 'lucide-react'; +import { useRef } from 'react'; +import { useAuth } from 'trails-app/lib/useAuth'; + +interface SearchBarProps { + value: string; + loading: boolean; + onChange: (value: string) => void; + onSubmit: (query: string) => void; +} + +export function SearchBar({ value, loading, onChange, onSubmit }: SearchBarProps) { + const { isAuthed, openAuthGate } = useAuth(); + const inputRef = useRef(null); + + function handleFocus() { + if (!isAuthed) { + inputRef.current?.blur(); + openAuthGate(); + } + } + + function handleKeyDown(e: React.KeyboardEvent) { + if (e.key === 'Enter' && isAuthed) { + onSubmit(value); + } + } + + return ( +
+ + {loading ? ( + + ) : null} + onChange(e.target.value)} + onFocus={handleFocus} + onKeyDown={handleKeyDown} + className="pl-9 pr-9" + readOnly={!isAuthed} + /> +
+ ); +} diff --git a/apps/trails/components/TrailCard.tsx b/apps/trails/components/TrailCard.tsx new file mode 100644 index 0000000000..b0bf7b9de2 --- /dev/null +++ b/apps/trails/components/TrailCard.tsx @@ -0,0 +1,73 @@ +'use client'; + +import { cn } from '@packrat/web-ui/lib/utils'; +import { MapPin, Mountain } from 'lucide-react'; +import type { TrailSummaryWithCoords } from 'trails-app/lib/overpass'; + +interface TrailCardProps { + trail: TrailSummaryWithCoords; + selected?: boolean; + onClick?: () => void; +} + +const SPORT_LABELS: Record = { + hiking: 'Hiking', + cycling: 'Cycling', + running: 'Running', + skiing: 'Skiing', + horse_riding: 'Horse Riding', +}; + +const DIFFICULTY_COLORS: Record = { + easy: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400', + moderate: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400', + hard: 'bg-orange-100 text-orange-800 dark:bg-orange-900/30 dark:text-orange-400', + expert: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400', +}; + +export function TrailCard({ trail, selected, onClick }: TrailCardProps) { + return ( + + ); +} diff --git a/apps/trails/components/TrailMap.tsx b/apps/trails/components/TrailMap.tsx new file mode 100644 index 0000000000..ad5401aac1 --- /dev/null +++ b/apps/trails/components/TrailMap.tsx @@ -0,0 +1,102 @@ +'use client'; + +import { useEffect, useRef } from 'react'; +import { DEFAULT_CENTER, DEFAULT_ZOOM, NEARBY_ZOOM } from 'trails-app/lib/geolocation'; +import type { TrailSummaryWithCoords } from 'trails-app/lib/overpass'; + +interface TrailMapProps { + center?: [number, number]; + trails: TrailSummaryWithCoords[]; + selectedOsmId?: string; + onTrailClick?: (osmId: string) => void; +} + +// Leaflet is SSR-incompatible; this component must be loaded via next/dynamic with ssr:false +export function TrailMap({ center, trails, selectedOsmId, onTrailClick }: TrailMapProps) { + const containerRef = useRef(null); + const mapRef = useRef(null); + const markersRef = useRef(null); + + // biome-ignore lint/correctness/useExhaustiveDependencies: intentionally runs once; center changes handled by flyTo effect below + useEffect(() => { + const container = containerRef.current; + if (!container) return; + + // Lazy-load Leaflet at runtime only (requires window) + let L: typeof import('leaflet'); + const initialCenter = center; + + async function init(el: HTMLDivElement) { + L = (await import('leaflet')).default; + + // Fix default icon paths broken by webpack/bun bundlers + // biome-ignore lint/suspicious/noExplicitAny: Leaflet internal + delete (L.Icon.Default.prototype as any)._getIconUrl; + L.Icon.Default.mergeOptions({ + iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', + iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', + shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', + }); + + if (mapRef.current) return; // already initialized + + const map = L.map(el, { + center: initialCenter ?? DEFAULT_CENTER, + zoom: initialCenter ? NEARBY_ZOOM : DEFAULT_ZOOM, + scrollWheelZoom: true, + }); + + L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { + attribution: + '© OpenStreetMap contributors', + maxZoom: 19, + }).addTo(map); + + markersRef.current = L.layerGroup().addTo(map); + mapRef.current = map; + } + + init(container); + + return () => { + mapRef.current?.remove(); + mapRef.current = null; + markersRef.current = null; + }; + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Fly to center when it changes (user location obtained) + useEffect(() => { + if (!mapRef.current || !center) return; + mapRef.current.flyTo(center, NEARBY_ZOOM, { duration: 1 }); + }, [center]); + + // Update markers when trails change + useEffect(() => { + if (!markersRef.current) return; + const group = markersRef.current; + group.clearLayers(); + + import('leaflet').then(({ default: L }) => { + for (const trail of trails) { + if (!trail.center) continue; + const isSelected = trail.osmId === selectedOsmId; + const marker = L.circleMarker(trail.center, { + radius: isSelected ? 10 : 7, + fillColor: isSelected ? '#6366f1' : '#3b82f6', + color: '#fff', + weight: 2, + opacity: 1, + fillOpacity: 0.9, + }); + marker.bindTooltip(trail.name ?? 'Unnamed Trail', { permanent: false, direction: 'top' }); + if (onTrailClick) { + marker.on('click', () => onTrailClick(trail.osmId)); + } + group.addLayer(marker); + } + }); + }, [trails, selectedOsmId, onTrailClick]); + + return
; +} diff --git a/apps/trails/components/TrailsPage.tsx b/apps/trails/components/TrailsPage.tsx new file mode 100644 index 0000000000..b7ceef4015 --- /dev/null +++ b/apps/trails/components/TrailsPage.tsx @@ -0,0 +1,241 @@ +'use client'; + +import { AlertCircle, MapPinOff } from 'lucide-react'; +import dynamic from 'next/dynamic'; +import { useCallback, useEffect, useRef, useState } from 'react'; +import { toast } from 'sonner'; +import { AuthGate } from 'trails-app/components/AuthGate'; +import { DownloadCTA } from 'trails-app/components/DownloadCTA'; +import { SearchBar } from 'trails-app/components/SearchBar'; +import { TrailCard } from 'trails-app/components/TrailCard'; +import { AuthExpiredError } from 'trails-app/lib/apiFetch'; +import { DEFAULT_CENTER, getUserLocation } from 'trails-app/lib/geolocation'; +import { loadNearbyTrails, type TrailSummaryWithCoords } from 'trails-app/lib/overpass'; +import { searchTrails, type TrailSearchParams } from 'trails-app/lib/trailSearch'; +import { useAuth } from 'trails-app/lib/useAuth'; + +// Leaflet requires window — load with ssr:false +const TrailMap = dynamic(() => import('trails-app/components/TrailMap').then((m) => m.TrailMap), { + ssr: false, + loading: () => ( +
+ ), +}); + +type MapState = + | { status: 'loading' } + | { status: 'idle'; center: [number, number] } + | { status: 'error'; message: string }; + +export function TrailsPage() { + const { isAuthed, openAuthGate } = useAuth(); + + const [mapState, setMapState] = useState({ status: 'loading' }); + const [mapCenter, setMapCenter] = useState<[number, number] | undefined>(); + const [publicTrails, setPublicTrails] = useState([]); + const [searchTrailResults, setSearchTrailResults] = useState( + null, + ); + const [searchLoading, setSearchLoading] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + const [selectedOsmId, setSelectedOsmId] = useState(); + const [hasMore, setHasMore] = useState(false); + const [offset, setOffset] = useState(0); + const lastSearchRef = useRef(null); + + // Load public trails via Overpass on mount + useEffect(() => { + let cancelled = false; + + async function loadMap() { + const coords = await getUserLocation(); + if (cancelled) return; + + const center = coords ?? DEFAULT_CENTER; + setMapCenter(center); + setMapState({ status: 'idle', center }); + + if (!coords) { + setMapState({ status: 'idle', center: DEFAULT_CENTER }); + return; + } + + try { + const trails = await loadNearbyTrails(center[0], center[1]); + if (!cancelled) setPublicTrails(trails); + } catch { + // Overpass failure is non-fatal; map still shows with no trails + if (!cancelled) setPublicTrails([]); + } + } + + loadMap(); + return () => { + cancelled = true; + }; + }, []); + + const runSearch = useCallback( + async (params: TrailSearchParams, append = false) => { + setSearchLoading(true); + try { + const { trails, hasMore: more } = await searchTrails(params); + setSearchTrailResults((prev) => (append && prev ? [...prev, ...trails] : trails)); + setHasMore(more); + setOffset((params.offset ?? 0) + trails.length); + lastSearchRef.current = params; + } catch (err) { + if (err instanceof AuthExpiredError) { + toast.error('Session expired. Please log in again.'); + openAuthGate(); + } else if (err instanceof Error && err.message.includes('429')) { + toast.error('Too many requests. Please wait a moment.'); + } else { + toast.error('Search failed. Please try again.'); + } + } finally { + setSearchLoading(false); + } + }, + [openAuthGate], + ); + + function handleSearch(query: string) { + if (!query.trim()) { + setSearchTrailResults(null); + return; + } + setOffset(0); + setHasMore(false); + runSearch({ q: query.trim(), limit: 20, offset: 0 }); + } + + function handleLoadMore() { + if (!lastSearchRef.current) return; + runSearch({ ...lastSearchRef.current, offset }, true); + } + + const displayedTrails = searchTrailResults ?? publicTrails; + const isSearchMode = searchTrailResults !== null; + + return ( +
+ {/* Header */} +
+
+ PackRat Trails +
+
+ +
+ {isAuthed && isSearchMode && ( + + )} +
+ + {/* Body: map + sidebar */} +
+ {/* Map */} +
+ {mapState.status === 'loading' ? ( +
+
Getting your location…
+
+ ) : ( + + )} + + {/* Search-to-login prompt overlay — shown when map is loaded but user is not authed */} + {!isAuthed && mapState.status === 'idle' && ( +
+ +
+ )} +
+ + {/* Trail list sidebar */} + +
+ + + +
+ ); +} diff --git a/apps/trails/components/VerifyEmail.tsx b/apps/trails/components/VerifyEmail.tsx new file mode 100644 index 0000000000..67443d8134 --- /dev/null +++ b/apps/trails/components/VerifyEmail.tsx @@ -0,0 +1,103 @@ +'use client'; + +import { OTPInput, REGEXP_ONLY_DIGITS } from 'input-otp'; +import { Loader2, Mail } from 'lucide-react'; +import { useCallback, useEffect, useState } from 'react'; +import { toast } from 'sonner'; +import { useAuth } from 'trails-app/lib/useAuth'; + +export function VerifyEmail() { + const { pendingEmail, verifyEmail, resendVerification } = useAuth(); + const [otp, setOtp] = useState(''); + const [loading, setLoading] = useState(false); + const [resendCooldown, setResendCooldown] = useState(0); + + useEffect(() => { + if (resendCooldown <= 0) return; + const timer = setTimeout(() => setResendCooldown((c) => c - 1), 1000); + return () => clearTimeout(timer); + }, [resendCooldown]); + + const handleComplete = useCallback( + async (value: string) => { + setLoading(true); + try { + await verifyEmail(value); + toast.success('Email verified! Search unlocked.'); + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Invalid code. Try again.'); + setOtp(''); + } finally { + setLoading(false); + } + }, + [verifyEmail], + ); + + const handleResend = useCallback(async () => { + try { + await resendVerification(); + setResendCooldown(60); + toast.success('Verification email sent!'); + } catch { + toast.error('Failed to resend. Try again.'); + } + }, [resendVerification]); + + return ( +
+
+ +
+
+

Check your email

+

+ We sent a 6-digit code to{' '} + {pendingEmail} +

+
+ + ( + <> + {slots.map((slot, i) => ( +
+ {slot.char ?? + (slot.isActive ? ( + | + ) : null)} +
+ ))} + + )} + /> + + {loading && } + +
+ {"Didn't receive it? "} + {resendCooldown > 0 ? ( + Resend in {resendCooldown}s + ) : ( + + )} +
+
+ ); +} diff --git a/apps/trails/components/ui/sonner.tsx b/apps/trails/components/ui/sonner.tsx new file mode 100644 index 0000000000..55915ce1fb --- /dev/null +++ b/apps/trails/components/ui/sonner.tsx @@ -0,0 +1,7 @@ +'use client'; + +import { Toaster as Sonner } from 'sonner'; + +export function Toaster() { + return ; +} diff --git a/apps/trails/lib/apiFetch.ts b/apps/trails/lib/apiFetch.ts new file mode 100644 index 0000000000..18fb75acdd --- /dev/null +++ b/apps/trails/lib/apiFetch.ts @@ -0,0 +1,47 @@ +import { + apiRefreshToken, + clearTokens, + clearUser, + getAccessToken, + getRefreshToken, + setTokens, +} from 'trails-app/lib/auth'; + +// Authenticated fetch with automatic token refresh on 401. +// On second 401 (refresh failed), clears auth and throws. +export async function authedFetch(input: string, init?: RequestInit): Promise { + const token = getAccessToken(); + const headers = new Headers(init?.headers); + if (token) headers.set('Authorization', `Bearer ${token}`); + + const res = await fetch(input, { ...init, headers }); + + if (res.status !== 401) return res; + + // Attempt token refresh + const refreshToken = getRefreshToken(); + if (!refreshToken) { + clearTokens(); + clearUser(); + throw new AuthExpiredError(); + } + + try { + const { accessToken, refreshToken: newRefresh } = await apiRefreshToken(refreshToken); + setTokens(accessToken, newRefresh); + // Retry original request with fresh token + headers.set('Authorization', `Bearer ${accessToken}`); + return fetch(input, { ...init, headers }); + } catch { + clearTokens(); + clearUser(); + throw new AuthExpiredError(); + } +} + +export class AuthExpiredError extends Error { + constructor() { + super('Session expired. Please log in again.'); + this.name = 'AuthExpiredError'; + } +} diff --git a/apps/trails/lib/auth.ts b/apps/trails/lib/auth.ts new file mode 100644 index 0000000000..38cdbf8f0d --- /dev/null +++ b/apps/trails/lib/auth.ts @@ -0,0 +1,140 @@ +// localStorage token storage following resilientTokenStorage pattern from web-support-mvp. +// atomWithStorage JSON-encodes values; raw JWTs may also be written directly. +// Always use these helpers — never read localStorage tokens raw. + +const ACCESS_KEY = 'access_token'; +const REFRESH_KEY = 'refresh_token'; + +function parseToken(raw: string | null): string | null { + if (!raw) return null; + try { + const parsed = JSON.parse(raw); + return typeof parsed === 'string' ? parsed : null; + } catch { + // Not JSON-encoded — return as-is (raw JWT) + return raw; + } +} + +export function getAccessToken(): string | null { + if (typeof window === 'undefined') return null; + return parseToken(localStorage.getItem(ACCESS_KEY)); +} + +export function getRefreshToken(): string | null { + if (typeof window === 'undefined') return null; + return parseToken(localStorage.getItem(REFRESH_KEY)); +} + +export function setTokens(accessToken: string, refreshToken: string): void { + localStorage.setItem(ACCESS_KEY, accessToken); + localStorage.setItem(REFRESH_KEY, refreshToken); +} + +export function clearTokens(): void { + localStorage.removeItem(ACCESS_KEY); + localStorage.removeItem(REFRESH_KEY); +} + +export interface UserInfo { + id: string; + email: string; + username?: string; +} + +export function setUser(user: UserInfo): void { + localStorage.setItem('user', JSON.stringify(user)); +} + +export function getUser(): UserInfo | null { + if (typeof window === 'undefined') return null; + try { + const raw = localStorage.getItem('user'); + return raw ? (JSON.parse(raw) as UserInfo) : null; + } catch { + return null; + } +} + +export function clearUser(): void { + localStorage.removeItem('user'); +} + +// --- API helpers --- + +const API_BASE = '/api'; + +export interface AuthResponse { + success: boolean; + accessToken?: string; + refreshToken?: string; + user?: UserInfo; + message?: string; + userId?: string; +} + +async function authFetch(path: string, body: Record): Promise { + const res = await fetch(`${API_BASE}${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); + const data = (await res.json()) as AuthResponse; + if (!res.ok) { + throw new Error((data as { message?: string }).message ?? `Request failed: ${res.status}`); + } + return data; +} + +export async function apiRegister(opts: { + email: string; + password: string; + username: string; +}): Promise<{ userId: string }> { + const data = await authFetch('/auth/register', opts); + return { userId: data.userId ?? '' }; +} + +export async function apiVerifyEmail( + email: string, + otp: string, +): Promise<{ accessToken: string; refreshToken: string; user: UserInfo }> { + const data = await authFetch('/auth/verify-email', { email, otp }); + if (!data.accessToken || !data.refreshToken || !data.user) { + throw new Error('Verification failed: missing token data'); + } + return { accessToken: data.accessToken, refreshToken: data.refreshToken, user: data.user }; +} + +export async function apiResendVerification(email: string): Promise { + await authFetch('/auth/resend-verification', { email }); +} + +export async function apiLogin( + email: string, + password: string, +): Promise<{ accessToken: string; refreshToken: string; user: UserInfo }> { + const data = await authFetch('/auth/login', { email, password }); + if (!data.accessToken || !data.refreshToken || !data.user) { + throw new Error('Login failed: missing token data'); + } + return { accessToken: data.accessToken, refreshToken: data.refreshToken, user: data.user }; +} + +export async function apiForgotPassword(email: string): Promise { + await authFetch('/auth/forgot-password', { email }); +} + +export async function apiRefreshToken( + refreshToken: string, +): Promise<{ accessToken: string; refreshToken: string }> { + const data = await authFetch('/auth/refresh', { refreshToken }); + if (!data.accessToken || !data.refreshToken) { + throw new Error('Token refresh failed'); + } + return { accessToken: data.accessToken, refreshToken: data.refreshToken }; +} + +export async function apiLogout(refreshToken: string): Promise { + await authFetch('/auth/logout', { refreshToken }); +} diff --git a/apps/trails/lib/geolocation.ts b/apps/trails/lib/geolocation.ts new file mode 100644 index 0000000000..b96ce5e88e --- /dev/null +++ b/apps/trails/lib/geolocation.ts @@ -0,0 +1,18 @@ +// Geographic center of the contiguous US — used as fallback when geolocation is denied +export const DEFAULT_CENTER: [number, number] = [39.5, -98.35]; +export const DEFAULT_ZOOM = 5; +export const NEARBY_ZOOM = 11; + +export function getUserLocation(): Promise<[number, number] | null> { + return new Promise((resolve) => { + if (typeof window === 'undefined' || !navigator.geolocation) { + resolve(null); + return; + } + navigator.geolocation.getCurrentPosition( + (pos) => resolve([pos.coords.latitude, pos.coords.longitude]), + () => resolve(null), + { timeout: 8000, maximumAge: 300_000 }, + ); + }); +} diff --git a/apps/trails/lib/overpass.ts b/apps/trails/lib/overpass.ts new file mode 100644 index 0000000000..6d2374b23c --- /dev/null +++ b/apps/trails/lib/overpass.ts @@ -0,0 +1,32 @@ +import { queryOverpass, TrailQueryBuilder, toTrailSummary } from '@packrat/overpass'; + +export interface TrailSummaryWithCoords { + osmId: string; + name: string | null; + sport: string | null; + distance: string | null; + difficulty: string | null; + network: string | null; + description: string | null; + bbox: [number, number, number, number] | null; + center: [number, number] | null; +} + +export async function loadNearbyTrails( + lat: number, + lon: number, +): Promise { + const ql = new TrailQueryBuilder().sport('hiking').around(lat, lon, 15_000).timeout(30).build(); + + const result = await queryOverpass(ql); + + return result.elements.map((el) => { + const summary = toTrailSummary(el); + let center: [number, number] | null = null; + if (summary.bbox) { + const [south, west, north, east] = summary.bbox; + center = [(south + north) / 2, (west + east) / 2]; + } + return { ...summary, center }; + }); +} diff --git a/apps/trails/lib/trailSearch.ts b/apps/trails/lib/trailSearch.ts new file mode 100644 index 0000000000..d6f84351a8 --- /dev/null +++ b/apps/trails/lib/trailSearch.ts @@ -0,0 +1,78 @@ +import { authedFetch } from 'trails-app/lib/apiFetch'; +import type { TrailSummaryWithCoords } from 'trails-app/lib/overpass'; + +export interface TrailSearchParams { + q?: string; + lat?: number; + lon?: number; + radius?: number; + sport?: string; + limit?: number; + offset?: number; +} + +export interface TrailSearchResult { + trails: TrailSummaryWithCoords[]; + hasMore: boolean; +} + +interface ApiTrail { + osmId: string; + name: string | null; + sport: string | null; + network: string | null; + distance: string | null; + difficulty: string | null; + description: string | null; + bbox: { coordinates?: number[][][][] } | null; +} + +function bboxCenter(bbox: ApiTrail['bbox']): [number, number] | null { + // bbox is GeoJSON Feature (ST_AsGeoJSON(ST_Envelope(geometry))) — extract centroid + if (!bbox?.coordinates?.[0]) return null; + const ring = bbox.coordinates[0]; + if (!ring) return null; + // ring is [[minLon, minLat], [maxLon, minLat], [maxLon, maxLat], [minLon, maxLat], [minLon, minLat]] + const lons = ring.flatMap((p) => (typeof p[0] === 'number' ? [p[0]] : [])); + const lats = ring.flatMap((p) => (typeof p[1] === 'number' ? [p[1]] : [])); + if (lons.length === 0 || lats.length === 0) return null; + const minLon = Math.min(...lons); + const maxLon = Math.max(...lons); + const minLat = Math.min(...lats); + const maxLat = Math.max(...lats); + return [(minLat + maxLat) / 2, (minLon + maxLon) / 2]; +} + +export async function searchTrails(params: TrailSearchParams): Promise { + const qs = new URLSearchParams(); + if (params.q) qs.set('q', params.q); + if (params.lat !== undefined) qs.set('lat', String(params.lat)); + if (params.lon !== undefined) qs.set('lon', String(params.lon)); + if (params.radius !== undefined) qs.set('radius', String(params.radius)); + if (params.sport) qs.set('sport', params.sport); + qs.set('limit', String(params.limit ?? 20)); + if (params.offset) qs.set('offset', String(params.offset)); + + const res = await authedFetch(`/api/trails/search?${qs.toString()}`); + + if (!res.ok) { + const body = (await res.json().catch(() => ({}))) as { message?: string }; + throw new Error(body.message ?? `Search failed: ${res.status}`); + } + + const data = (await res.json()) as { trails: ApiTrail[]; hasMore: boolean }; + + const trails: TrailSummaryWithCoords[] = data.trails.map((t) => ({ + osmId: t.osmId, + name: t.name, + sport: t.sport, + network: t.network, + distance: t.distance, + difficulty: t.difficulty, + description: t.description, + bbox: null, // not needed client-side after we extract center + center: bboxCenter(t.bbox), + })); + + return { trails, hasMore: data.hasMore }; +} diff --git a/apps/trails/lib/useAuth.tsx b/apps/trails/lib/useAuth.tsx new file mode 100644 index 0000000000..f911a4c96b --- /dev/null +++ b/apps/trails/lib/useAuth.tsx @@ -0,0 +1,139 @@ +'use client'; + +import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'; +import { + apiLogin, + apiLogout, + apiRegister, + apiResendVerification, + apiVerifyEmail, + clearTokens, + clearUser, + getAccessToken, + getRefreshToken, + getUser, + setTokens, + setUser, + type UserInfo, +} from 'trails-app/lib/auth'; + +interface AuthState { + isAuthed: boolean; + user: UserInfo | null; + // Pending verification: user registered but hasn't verified email yet + pendingEmail: string | null; +} + +interface AuthActions { + register(email: string, opts: { password: string; username: string }): Promise; + verifyEmail(otp: string): Promise; + resendVerification(): Promise; + login(email: string, password: string): Promise; + logout(): Promise; + openAuthGate(): void; + closeAuthGate(): void; + authGateOpen: boolean; +} + +const AuthContext = createContext<(AuthState & AuthActions) | null>(null); + +export function AuthProvider({ children }: { children: React.ReactNode }) { + const [state, setState] = useState({ + isAuthed: false, + user: null, + pendingEmail: null, + }); + const [authGateOpen, setAuthGateOpen] = useState(false); + + // Hydrate from localStorage on mount + useEffect(() => { + const token = getAccessToken(); + const user = getUser(); + if (token && user) { + setState({ isAuthed: true, user, pendingEmail: null }); + } + }, []); + + const register = useCallback( + async (email: string, opts: { password: string; username: string }) => { + await apiRegister({ email, password: opts.password, username: opts.username }); + setState((s) => ({ ...s, pendingEmail: email })); + }, + [], + ); + + const verifyEmail = useCallback( + async (otp: string) => { + if (!state.pendingEmail) throw new Error('No pending email verification'); + const { accessToken, refreshToken, user } = await apiVerifyEmail(state.pendingEmail, otp); + setTokens(accessToken, refreshToken); + setUser(user); + setState({ isAuthed: true, user, pendingEmail: null }); + setAuthGateOpen(false); + }, + [state.pendingEmail], + ); + + const resendVerification = useCallback(async () => { + if (!state.pendingEmail) throw new Error('No pending email'); + await apiResendVerification(state.pendingEmail); + }, [state.pendingEmail]); + + const login = useCallback(async (email: string, password: string) => { + const { accessToken, refreshToken, user } = await apiLogin(email, password); + setTokens(accessToken, refreshToken); + setUser(user); + setState({ isAuthed: true, user, pendingEmail: null }); + setAuthGateOpen(false); + }, []); + + const logout = useCallback(async () => { + const refreshToken = getRefreshToken(); + if (refreshToken) { + try { + await apiLogout(refreshToken); + } catch { + // ignore — clear tokens regardless + } + } + clearTokens(); + clearUser(); + setState({ isAuthed: false, user: null, pendingEmail: null }); + }, []); + + const openAuthGate = useCallback(() => setAuthGateOpen(true), []); + const closeAuthGate = useCallback(() => setAuthGateOpen(false), []); + + const value = useMemo( + () => ({ + ...state, + authGateOpen, + register, + verifyEmail, + resendVerification, + login, + logout, + openAuthGate, + closeAuthGate, + }), + [ + state, + authGateOpen, + register, + verifyEmail, + resendVerification, + login, + logout, + openAuthGate, + closeAuthGate, + ], + ); + + return {children}; +} + +export function useAuth(): AuthState & AuthActions { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error('useAuth must be used within AuthProvider'); + return ctx; +} diff --git a/apps/trails/next.config.mjs b/apps/trails/next.config.mjs new file mode 100644 index 0000000000..37d7999b3e --- /dev/null +++ b/apps/trails/next.config.mjs @@ -0,0 +1,16 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + output: 'export', + eslint: { + ignoreDuringBuilds: true, + }, + typescript: { + ignoreBuildErrors: true, + }, + images: { + unoptimized: true, + }, + transpilePackages: ['@packrat/web-ui', '@packrat/overpass'], +}; + +export default nextConfig; diff --git a/apps/trails/package.json b/apps/trails/package.json new file mode 100644 index 0000000000..73a1569bbf --- /dev/null +++ b/apps/trails/package.json @@ -0,0 +1,47 @@ +{ + "name": "packrat-trails-app", + "version": "2.0.24", + "private": true, + "scripts": { + "build": "next build", + "clean": "bunx rimraf node_modules .next out", + "deploy": "wrangler deploy", + "dev": "next dev", + "lint": "next lint", + "start": "next start" + }, + "dependencies": { + "@packrat/guards": "workspace:*", + "@packrat/overpass": "workspace:*", + "@packrat/web-ui": "workspace:*", + "@radix-ui/react-dialog": "catalog:", + "@radix-ui/react-label": "catalog:", + "@radix-ui/react-separator": "catalog:", + "@radix-ui/react-tabs": "catalog:", + "@radix-ui/react-toast": "catalog:", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "input-otp": "1.4.1", + "leaflet": "^1.9.4", + "lucide-react": "^1.8.0", + "next": "^15.3.4", + "react": "catalog:", + "react-dom": "catalog:", + "react-leaflet": "^5.0.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "zod": "catalog:" + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250620.0", + "@types/leaflet": "^1.9.17", + "@types/node": "^25.6.0", + "@types/react": "~19.2.10", + "@types/react-dom": "^19.1.6", + "postcss": "^8.5.6", + "postcss-import": "^16.1.1", + "tailwindcss": "catalog:", + "typescript": "catalog:", + "wrangler": "^4.21.1" + } +} diff --git a/apps/trails/postcss.config.mjs b/apps/trails/postcss.config.mjs new file mode 100644 index 0000000000..ad5a713429 --- /dev/null +++ b/apps/trails/postcss.config.mjs @@ -0,0 +1,9 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + 'postcss-import': {}, + tailwindcss: {}, + }, +}; + +export default config; diff --git a/apps/trails/tailwind.config.ts b/apps/trails/tailwind.config.ts new file mode 100644 index 0000000000..6afa3406cd --- /dev/null +++ b/apps/trails/tailwind.config.ts @@ -0,0 +1,16 @@ +import preset from '@packrat/web-ui/tailwind/preset'; +import type { Config } from 'tailwindcss'; + +const config = { + presets: [preset], + content: [ + './pages/**/*.{ts,tsx}', + './components/**/*.{ts,tsx}', + './app/**/*.{ts,tsx}', + './src/**/*.{ts,tsx}', + '*.{js,ts,jsx,tsx,mdx}', + '../../packages/web-ui/src/**/*.{ts,tsx}', + ], +} satisfies Config; + +export default config; diff --git a/apps/trails/tsconfig.json b/apps/trails/tsconfig.json new file mode 100644 index 0000000000..bc1eab1397 --- /dev/null +++ b/apps/trails/tsconfig.json @@ -0,0 +1,32 @@ +{ + "compilerOptions": { + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "target": "ES6", + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "trails-app/*": ["./*"], + "@packrat/api/*": ["../../packages/api/src/*"], + "@packrat/overpass": ["../../packages/overpass/src/index.ts"], + "@packrat/overpass/*": ["../../packages/overpass/src/*"], + "@packrat/web-ui": ["../../packages/web-ui/src"], + "@packrat/web-ui/*": ["../../packages/web-ui/src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/apps/trails/worker/index.ts b/apps/trails/worker/index.ts new file mode 100644 index 0000000000..49b3e2deda --- /dev/null +++ b/apps/trails/worker/index.ts @@ -0,0 +1,86 @@ +interface Env { + ASSETS: Fetcher; + RATE_LIMITER: { limit(opts: { key: string }): Promise<{ success: boolean }> } | undefined; + PACKRAT_API_BASE_URL: string; +} + +const TRAIL_DETAIL_RE = /^\/api\/trails\/[^/]+$/; + +const CORS_HEADERS = { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', +}; + +function corsResponse(status: number, body: string): Response { + return new Response(body, { + status, + headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, + }); +} + +async function proxyToApi(request: Request, env: Env): Promise { + const url = new URL(request.url); + + // Rate limit by IP + if (env.RATE_LIMITER) { + const ip = + request.headers.get('CF-Connecting-IP') ?? + request.headers.get('X-Forwarded-For') ?? + 'unknown'; + const { success } = await env.RATE_LIMITER.limit({ key: ip }); + if (!success) { + return corsResponse( + 429, + JSON.stringify({ error: 'Too many requests. Please try again in a moment.' }), + ); + } + } + + // Handle CORS preflight + if (request.method === 'OPTIONS') { + return new Response(null, { status: 204, headers: CORS_HEADERS }); + } + + // Build upstream URL + const upstream = new URL(url.pathname + url.search, env.PACKRAT_API_BASE_URL); + + // Forward request with same headers (preserves Authorization Bearer token from client) + const proxyRequest = new Request(upstream.toString(), { + method: request.method, + headers: request.headers, + body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : null, + }); + + try { + const response = await fetch(proxyRequest); + const responseBody = await response.text(); + + // Add CORS headers to the proxied response + const headers = new Headers(response.headers); + for (const [key, value] of Object.entries(CORS_HEADERS)) { + headers.set(key, value); + } + + // Cache trail detail responses at edge (~1 hour TTL for non-search requests) + if (TRAIL_DETAIL_RE.test(url.pathname) && request.method === 'GET') { + headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=600'); + } + + return new Response(responseBody, { status: response.status, headers }); + } catch { + return corsResponse(502, JSON.stringify({ error: 'API unavailable. Please try again later.' })); + } +} + +export default { + async fetch(request: Request, env: Env): Promise { + const url = new URL(request.url); + + if (url.pathname.startsWith('/api/')) { + return proxyToApi(request, env); + } + + return env.ASSETS.fetch(request); + }, +} satisfies ExportedHandler; diff --git a/apps/trails/wrangler.jsonc b/apps/trails/wrangler.jsonc new file mode 100644 index 0000000000..9d99f069d5 --- /dev/null +++ b/apps/trails/wrangler.jsonc @@ -0,0 +1,28 @@ +{ + "$schema": "https://developers.cloudflare.com/schemas/wrangler.json", + "name": "packrat-trails", + "compatibility_date": "2025-06-01", + // Worker fetch handler: proxies /api/* to PackRat API; all other requests served from static assets + "main": "./worker/index.ts", + "assets": { + "directory": "./out", + "not_found_handling": "404-page" + }, + // Rate limiting: 60 requests per IP per 60 seconds + // Create namespace: wrangler rate-limit create --simple --limit 60 --period 60 + // Then replace namespace_id below with the returned ID + "rate_limiting": [ + { + "binding": "RATE_LIMITER", + "namespace_id": "__REPLACE_WITH_NAMESPACE_ID__", + "simple": { + "limit": 60, + "period": 60 + } + } + ], + "vars": { + // Override in Cloudflare dashboard for production; use .dev.vars locally + "PACKRAT_API_BASE_URL": "https://api.packratai.com" + } +} diff --git a/bun.lock b/bun.lock index 917b28da45..7b47b0fb2c 100644 --- a/bun.lock +++ b/bun.lock @@ -338,6 +338,44 @@ "typescript": "catalog:", }, }, + "apps/trails": { + "name": "packrat-trails-app", + "version": "2.0.24", + "dependencies": { + "@packrat/guards": "workspace:*", + "@packrat/overpass": "workspace:*", + "@packrat/web-ui": "workspace:*", + "@radix-ui/react-dialog": "catalog:", + "@radix-ui/react-label": "catalog:", + "@radix-ui/react-separator": "catalog:", + "@radix-ui/react-tabs": "catalog:", + "@radix-ui/react-toast": "catalog:", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "input-otp": "1.4.1", + "leaflet": "^1.9.4", + "lucide-react": "^1.8.0", + "next": "^15.3.4", + "react": "catalog:", + "react-dom": "catalog:", + "react-leaflet": "^5.0.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.5.0", + "zod": "catalog:", + }, + "devDependencies": { + "@cloudflare/workers-types": "^4.20250620.0", + "@types/leaflet": "^1.9.17", + "@types/node": "^25.6.0", + "@types/react": "~19.2.10", + "@types/react-dom": "^19.1.6", + "postcss": "^8.5.6", + "postcss-import": "^16.1.1", + "tailwindcss": "catalog:", + "typescript": "catalog:", + "wrangler": "^4.21.1", + }, + }, "packages/analytics": { "name": "@packrat/analytics", "version": "2.0.24", @@ -3297,6 +3335,8 @@ "packrat-landing-app": ["packrat-landing-app@workspace:apps/landing"], + "packrat-trails-app": ["packrat-trails-app@workspace:apps/trails"], + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], "parse-github-url": ["parse-github-url@1.0.4", "", { "bin": { "parse-github-url": "cli.js" } }, "sha512-CEtCOt55fHmd6DpBc/N7H5NC4vJpcquhzzs9Iw2mRj8bVxo1O5TQI5MXKOMO7+yBOqD+5dKCCRK4Kj1KskZc6Q=="], diff --git a/package.json b/package.json index 1d449b88e8..41e671f656 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ ], "scripts": { "admin": "bun run --cwd apps/admin dev", + "trails": "bun run --cwd apps/trails dev", "android": "cd apps/expo && bun android", "api": "bun run --cwd packages/api dev", "bump": "bun .github/scripts/bump.ts", diff --git a/tsconfig.json b/tsconfig.json index 8d3088ab3a..d3c7dce925 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,9 @@ "admin-app/*": ["./apps/admin/*"], "guides-app/*": ["./apps/guides/*"], "landing-app/*": ["./apps/landing/*"], + "trails-app/*": ["./apps/trails/*"], + "@packrat/overpass": ["./packages/overpass/src/index.ts"], + "@packrat/overpass/*": ["./packages/overpass/src/*"], "expo-app/*": ["./apps/expo/*"], "app/*": ["./packages/app/*"], "config/*": ["./packages/config/*"], From 2419785c5fe19d2193fa5572bb94a5631bd319fb Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 01:38:50 -0600 Subject: [PATCH 02/21] fix(trails): use isString guard instead of raw typeof in parseToken MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/trails/lib/auth.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/trails/lib/auth.ts b/apps/trails/lib/auth.ts index 38cdbf8f0d..e88c3d4dbf 100644 --- a/apps/trails/lib/auth.ts +++ b/apps/trails/lib/auth.ts @@ -2,6 +2,8 @@ // atomWithStorage JSON-encodes values; raw JWTs may also be written directly. // Always use these helpers — never read localStorage tokens raw. +import { isString } from '@packrat/guards'; + const ACCESS_KEY = 'access_token'; const REFRESH_KEY = 'refresh_token'; @@ -9,7 +11,7 @@ function parseToken(raw: string | null): string | null { if (!raw) return null; try { const parsed = JSON.parse(raw); - return typeof parsed === 'string' ? parsed : null; + return isString(parsed) ? parsed : null; } catch { // Not JSON-encoded — return as-is (raw JWT) return raw; From 41a02cc1ef3f8349f2f48f95b41e3f59332615e4 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 01:41:18 -0600 Subject: [PATCH 03/21] fix(trails): align devDependency versions with monorepo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit workers-types ^4.20250620.0→^4.20250405.0, @types/leaflet ^1.9.17→^1.9.21, wrangler ^4.21.1→^4.21.2 --- apps/trails/package.json | 6 +++--- bun.lock | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/trails/package.json b/apps/trails/package.json index 73a1569bbf..92d7de1ca4 100644 --- a/apps/trails/package.json +++ b/apps/trails/package.json @@ -33,8 +33,8 @@ "zod": "catalog:" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250620.0", - "@types/leaflet": "^1.9.17", + "@cloudflare/workers-types": "^4.20250405.0", + "@types/leaflet": "^1.9.21", "@types/node": "^25.6.0", "@types/react": "~19.2.10", "@types/react-dom": "^19.1.6", @@ -42,6 +42,6 @@ "postcss-import": "^16.1.1", "tailwindcss": "catalog:", "typescript": "catalog:", - "wrangler": "^4.21.1" + "wrangler": "^4.21.2" } } diff --git a/bun.lock b/bun.lock index 7b47b0fb2c..694a937692 100644 --- a/bun.lock +++ b/bun.lock @@ -364,8 +364,8 @@ "zod": "catalog:", }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250620.0", - "@types/leaflet": "^1.9.17", + "@cloudflare/workers-types": "^4.20250405.0", + "@types/leaflet": "^1.9.21", "@types/node": "^25.6.0", "@types/react": "~19.2.10", "@types/react-dom": "^19.1.6", @@ -373,7 +373,7 @@ "postcss-import": "^16.1.1", "tailwindcss": "catalog:", "typescript": "catalog:", - "wrangler": "^4.21.1", + "wrangler": "^4.21.2", }, }, "packages/analytics": { From dfe3f579c958eaa584b2e1ef9a6acfd984353861 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 01:41:29 -0600 Subject: [PATCH 04/21] chore: sort root package.json keys --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 41e671f656..91f13a9c89 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,6 @@ ], "scripts": { "admin": "bun run --cwd apps/admin dev", - "trails": "bun run --cwd apps/trails dev", "android": "cd apps/expo && bun android", "api": "bun run --cwd packages/api dev", "bump": "bun .github/scripts/bump.ts", @@ -46,7 +45,8 @@ "test:e2e:ios": "bash .github/scripts/e2e.sh ios", "test:expo": "vitest run --config apps/expo/vitest.config.ts", "test:expo:rpc-types": "vitest run --config apps/expo/vitest.types.config.ts", - "test:mcp": "bun run --cwd packages/mcp test" + "test:mcp": "bun run --cwd packages/mcp test", + "trails": "bun run --cwd apps/trails dev" }, "overrides": { "@sinclair/typebox": "^0.34.15", From ff7a1dfd494708cd7238988e8d91fe74e4448e24 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 01:44:25 -0600 Subject: [PATCH 05/21] fix(trails): replace unsafe as-casts with fromZod + makeEnumGuard UserInfo/AuthResponse now use zod schemas; Tab narrowing uses makeEnumGuard. --- apps/trails/components/AuthGate.tsx | 12 +++++++-- apps/trails/lib/auth.ts | 39 ++++++++++++++++------------- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/apps/trails/components/AuthGate.tsx b/apps/trails/components/AuthGate.tsx index 6e3ae8c7a1..aa897485ab 100644 --- a/apps/trails/components/AuthGate.tsx +++ b/apps/trails/components/AuthGate.tsx @@ -1,5 +1,6 @@ 'use client'; +import { makeEnumGuard } from '@packrat/guards'; import { Button } from '@packrat/web-ui'; import { Dialog, @@ -18,7 +19,9 @@ import { VerifyEmail } from 'trails-app/components/VerifyEmail'; import { apiForgotPassword } from 'trails-app/lib/auth'; import { useAuth } from 'trails-app/lib/useAuth'; -type Tab = 'register' | 'login' | 'forgot'; +const TABS = ['register', 'login', 'forgot'] as const; +type Tab = (typeof TABS)[number]; +const isTab = makeEnumGuard(TABS); export function AuthGate() { const { authGateOpen, closeAuthGate, register, login, pendingEmail } = useAuth(); @@ -100,7 +103,12 @@ export function AuthGate() { {pendingEmail ? ( ) : ( - setTab(v as Tab)}> + { + if (isTab(v)) setTab(v); + }} + > Create account Log in diff --git a/apps/trails/lib/auth.ts b/apps/trails/lib/auth.ts index e88c3d4dbf..e2c793f920 100644 --- a/apps/trails/lib/auth.ts +++ b/apps/trails/lib/auth.ts @@ -2,7 +2,8 @@ // atomWithStorage JSON-encodes values; raw JWTs may also be written directly. // Always use these helpers — never read localStorage tokens raw. -import { isString } from '@packrat/guards'; +import { fromZod, isString } from '@packrat/guards'; +import z from 'zod'; const ACCESS_KEY = 'access_token'; const REFRESH_KEY = 'refresh_token'; @@ -38,11 +39,13 @@ export function clearTokens(): void { localStorage.removeItem(REFRESH_KEY); } -export interface UserInfo { - id: string; - email: string; - username?: string; -} +const UserInfoSchema = z.object({ + id: z.string(), + email: z.string(), + username: z.string().optional(), +}); + +export type UserInfo = z.infer; export function setUser(user: UserInfo): void { localStorage.setItem('user', JSON.stringify(user)); @@ -52,7 +55,7 @@ export function getUser(): UserInfo | null { if (typeof window === 'undefined') return null; try { const raw = localStorage.getItem('user'); - return raw ? (JSON.parse(raw) as UserInfo) : null; + return raw ? (fromZod(UserInfoSchema)(JSON.parse(raw)) ?? null) : null; } catch { return null; } @@ -66,14 +69,16 @@ export function clearUser(): void { const API_BASE = '/api'; -export interface AuthResponse { - success: boolean; - accessToken?: string; - refreshToken?: string; - user?: UserInfo; - message?: string; - userId?: string; -} +const AuthResponseSchema = z.object({ + success: z.boolean().optional(), + accessToken: z.string().optional(), + refreshToken: z.string().optional(), + user: UserInfoSchema.optional(), + message: z.string().optional(), + userId: z.string().optional(), +}); + +export type AuthResponse = z.infer; async function authFetch(path: string, body: Record): Promise { const res = await fetch(`${API_BASE}${path}`, { @@ -81,9 +86,9 @@ async function authFetch(path: string, body: Record): Promise Date: Thu, 7 May 2026 08:11:18 -0600 Subject: [PATCH 06/21] refactor(trails): swap manual fetch wrappers for @packrat/api-client MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add lib/apiClient.ts: Treaty client wired to same-origin proxy so CF Worker rate limiting applies - Strip lib/auth.ts to storage helpers only (UserInfo now matches API shape: id:number, firstName/lastName) - Delete lib/apiFetch.ts (AuthExpiredError moved to apiClient.ts) - Rewrite lib/trailSearch.ts and lib/useAuth.tsx using typed Treaty endpoints - Update AuthGate.tsx: username→firstName field, forgot-password via apiClient - Update CLAUDE.md with @packrat/api-client usage pattern for all web apps --- CLAUDE.md | 29 ++++++++- apps/trails/components/AuthGate.tsx | 21 +++---- apps/trails/components/TrailsPage.tsx | 3 +- apps/trails/lib/apiClient.ts | 38 ++++++++++++ apps/trails/lib/apiFetch.ts | 47 -------------- apps/trails/lib/auth.ts | 88 ++------------------------- apps/trails/lib/trailSearch.ts | 49 ++++++++------- apps/trails/lib/useAuth.tsx | 60 +++++++++++++----- apps/trails/package.json | 1 + bun.lock | 1 + 10 files changed, 154 insertions(+), 183 deletions(-) create mode 100644 apps/trails/lib/apiClient.ts delete mode 100644 apps/trails/lib/apiFetch.ts diff --git a/CLAUDE.md b/CLAUDE.md index f62d6a47db..6d251daa35 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -96,12 +96,39 @@ features/{name}/ - **Feature flags**: `apps/expo/config.ts` — `featureFlags` object, default new flags to `false` - **Animations**: React Native Reanimated 4 -### Web Apps (apps/guides, apps/landing) +### Web Apps (apps/guides, apps/landing, apps/trails) - Radix UI + Shadcn components, Tailwind CSS - TanStack React Query for data fetching - Zod for form validation +### API Client (`@packrat/api-client`) + +Use `createApiClient` from `@packrat/api-client` for all PackRat API calls in web apps. **Never write manual fetch wrappers for PackRat API endpoints.** + +```ts +// apps//lib/apiClient.ts +import { createApiClient } from '@packrat/api-client'; +import { clearTokens, clearUser, getAccessToken, getRefreshToken, setTokens } from './auth'; + +export const apiClient = createApiClient({ + baseUrl: typeof window !== 'undefined' ? window.location.origin : '', + auth: { + getAccessToken, + getRefreshToken, + onAccessTokenRefreshed: (token) => { /* persist new access token */ }, + onRefreshTokenRefreshed: (token) => { /* persist new refresh token */ }, + onNeedsReauth: () => { clearTokens(); clearUser(); }, + }, +}); +``` + +- `baseUrl` should be the same origin when routing through a CF Worker proxy (so rate limiting applies); use `EXPO_PUBLIC_API_URL` for the Expo app +- `AuthHooks` wires your platform's token storage — the package is transport-only +- The client handles 401 → refresh → retry automatically; `onNeedsReauth` fires only when refresh itself fails +- Call via Treaty path syntax: `apiClient.auth.login.post(...)`, `apiClient.trails.search.get({ query: { q } })` +- Responses are `{ data, error, status }` — check `if (error || !data)` before using `data` + ## Private Package Auth `@packrat-ai/nativewindui` is hosted on GitHub Packages. `bunfig.toml` resolves the scope using `$PACKRAT_NATIVEWIND_UI_GITHUB_TOKEN`. Bun auto-loads `.env.local` before running `install`, so the simplest setup is to put the token there alongside your other secrets. diff --git a/apps/trails/components/AuthGate.tsx b/apps/trails/components/AuthGate.tsx index aa897485ab..0967e46c7f 100644 --- a/apps/trails/components/AuthGate.tsx +++ b/apps/trails/components/AuthGate.tsx @@ -16,7 +16,7 @@ import { Loader2 } from 'lucide-react'; import { useState } from 'react'; import { toast } from 'sonner'; import { VerifyEmail } from 'trails-app/components/VerifyEmail'; -import { apiForgotPassword } from 'trails-app/lib/auth'; +import { apiClient } from 'trails-app/lib/apiClient'; import { useAuth } from 'trails-app/lib/useAuth'; const TABS = ['register', 'login', 'forgot'] as const; @@ -31,7 +31,7 @@ export function AuthGate() { // Register form const [regEmail, setRegEmail] = useState(''); const [regPassword, setRegPassword] = useState(''); - const [regUsername, setRegUsername] = useState(''); + const [regFirstName, setRegFirstName] = useState(''); // Login form const [loginEmail, setLoginEmail] = useState(''); @@ -45,7 +45,7 @@ export function AuthGate() { e.preventDefault(); setLoading(true); try { - await register(regEmail, { password: regPassword, username: regUsername }); + await register(regEmail, { password: regPassword, firstName: regFirstName || undefined }); } catch (err) { const msg = err instanceof Error ? err.message : 'Registration failed'; if (msg.toLowerCase().includes('already') || msg.toLowerCase().includes('exists')) { @@ -77,7 +77,7 @@ export function AuthGate() { e.preventDefault(); setLoading(true); try { - await apiForgotPassword(forgotEmail); + await apiClient.auth['forgot-password'].post({ email: forgotEmail }); setForgotSent(true); } catch { toast.error('Could not send reset email. Try again.'); @@ -117,14 +117,13 @@ export function AuthGate() {
- + setRegUsername(e.target.value)} - required - autoComplete="username" + id="reg-name" + placeholder="Trail Blazer" + value={regFirstName} + onChange={(e) => setRegFirstName(e.target.value)} + autoComplete="given-name" />
diff --git a/apps/trails/components/TrailsPage.tsx b/apps/trails/components/TrailsPage.tsx index b7ceef4015..8542d9ffb3 100644 --- a/apps/trails/components/TrailsPage.tsx +++ b/apps/trails/components/TrailsPage.tsx @@ -8,10 +8,9 @@ import { AuthGate } from 'trails-app/components/AuthGate'; import { DownloadCTA } from 'trails-app/components/DownloadCTA'; import { SearchBar } from 'trails-app/components/SearchBar'; import { TrailCard } from 'trails-app/components/TrailCard'; -import { AuthExpiredError } from 'trails-app/lib/apiFetch'; import { DEFAULT_CENTER, getUserLocation } from 'trails-app/lib/geolocation'; import { loadNearbyTrails, type TrailSummaryWithCoords } from 'trails-app/lib/overpass'; -import { searchTrails, type TrailSearchParams } from 'trails-app/lib/trailSearch'; +import { AuthExpiredError, searchTrails, type TrailSearchParams } from 'trails-app/lib/trailSearch'; import { useAuth } from 'trails-app/lib/useAuth'; // Leaflet requires window — load with ssr:false diff --git a/apps/trails/lib/apiClient.ts b/apps/trails/lib/apiClient.ts new file mode 100644 index 0000000000..846c0d727c --- /dev/null +++ b/apps/trails/lib/apiClient.ts @@ -0,0 +1,38 @@ +'use client'; + +import { createApiClient } from '@packrat/api-client'; +import { + clearTokens, + clearUser, + getAccessToken, + getRefreshToken, + setTokens, +} from 'trails-app/lib/auth'; + +// Routes through the same-origin CF Worker proxy (/api/*) so rate limiting applies. +export const apiClient = createApiClient({ + baseUrl: typeof window !== 'undefined' ? window.location.origin : '', + auth: { + getAccessToken, + getRefreshToken, + onAccessTokenRefreshed: (token) => { + const refresh = getRefreshToken(); + if (refresh) setTokens(token, refresh); + }, + onRefreshTokenRefreshed: (token) => { + const access = getAccessToken(); + if (access) setTokens(access, token); + }, + onNeedsReauth: () => { + clearTokens(); + clearUser(); + }, + }, +}); + +export class AuthExpiredError extends Error { + constructor() { + super('Session expired. Please log in again.'); + this.name = 'AuthExpiredError'; + } +} diff --git a/apps/trails/lib/apiFetch.ts b/apps/trails/lib/apiFetch.ts deleted file mode 100644 index 18fb75acdd..0000000000 --- a/apps/trails/lib/apiFetch.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { - apiRefreshToken, - clearTokens, - clearUser, - getAccessToken, - getRefreshToken, - setTokens, -} from 'trails-app/lib/auth'; - -// Authenticated fetch with automatic token refresh on 401. -// On second 401 (refresh failed), clears auth and throws. -export async function authedFetch(input: string, init?: RequestInit): Promise { - const token = getAccessToken(); - const headers = new Headers(init?.headers); - if (token) headers.set('Authorization', `Bearer ${token}`); - - const res = await fetch(input, { ...init, headers }); - - if (res.status !== 401) return res; - - // Attempt token refresh - const refreshToken = getRefreshToken(); - if (!refreshToken) { - clearTokens(); - clearUser(); - throw new AuthExpiredError(); - } - - try { - const { accessToken, refreshToken: newRefresh } = await apiRefreshToken(refreshToken); - setTokens(accessToken, newRefresh); - // Retry original request with fresh token - headers.set('Authorization', `Bearer ${accessToken}`); - return fetch(input, { ...init, headers }); - } catch { - clearTokens(); - clearUser(); - throw new AuthExpiredError(); - } -} - -export class AuthExpiredError extends Error { - constructor() { - super('Session expired. Please log in again.'); - this.name = 'AuthExpiredError'; - } -} diff --git a/apps/trails/lib/auth.ts b/apps/trails/lib/auth.ts index e2c793f920..e906df5228 100644 --- a/apps/trails/lib/auth.ts +++ b/apps/trails/lib/auth.ts @@ -39,10 +39,11 @@ export function clearTokens(): void { localStorage.removeItem(REFRESH_KEY); } -const UserInfoSchema = z.object({ - id: z.string(), +export const UserInfoSchema = z.object({ + id: z.number(), email: z.string(), - username: z.string().optional(), + firstName: z.string().nullish(), + lastName: z.string().nullish(), }); export type UserInfo = z.infer; @@ -64,84 +65,3 @@ export function getUser(): UserInfo | null { export function clearUser(): void { localStorage.removeItem('user'); } - -// --- API helpers --- - -const API_BASE = '/api'; - -const AuthResponseSchema = z.object({ - success: z.boolean().optional(), - accessToken: z.string().optional(), - refreshToken: z.string().optional(), - user: UserInfoSchema.optional(), - message: z.string().optional(), - userId: z.string().optional(), -}); - -export type AuthResponse = z.infer; - -async function authFetch(path: string, body: Record): Promise { - const res = await fetch(`${API_BASE}${path}`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(body), - }); - const data = fromZod(AuthResponseSchema)(await res.json()) ?? {}; - if (!res.ok) { - throw new Error(data.message ?? `Request failed: ${res.status}`); - } - return data; -} - -export async function apiRegister(opts: { - email: string; - password: string; - username: string; -}): Promise<{ userId: string }> { - const data = await authFetch('/auth/register', opts); - return { userId: data.userId ?? '' }; -} - -export async function apiVerifyEmail( - email: string, - otp: string, -): Promise<{ accessToken: string; refreshToken: string; user: UserInfo }> { - const data = await authFetch('/auth/verify-email', { email, otp }); - if (!data.accessToken || !data.refreshToken || !data.user) { - throw new Error('Verification failed: missing token data'); - } - return { accessToken: data.accessToken, refreshToken: data.refreshToken, user: data.user }; -} - -export async function apiResendVerification(email: string): Promise { - await authFetch('/auth/resend-verification', { email }); -} - -export async function apiLogin( - email: string, - password: string, -): Promise<{ accessToken: string; refreshToken: string; user: UserInfo }> { - const data = await authFetch('/auth/login', { email, password }); - if (!data.accessToken || !data.refreshToken || !data.user) { - throw new Error('Login failed: missing token data'); - } - return { accessToken: data.accessToken, refreshToken: data.refreshToken, user: data.user }; -} - -export async function apiForgotPassword(email: string): Promise { - await authFetch('/auth/forgot-password', { email }); -} - -export async function apiRefreshToken( - refreshToken: string, -): Promise<{ accessToken: string; refreshToken: string }> { - const data = await authFetch('/auth/refresh', { refreshToken }); - if (!data.accessToken || !data.refreshToken) { - throw new Error('Token refresh failed'); - } - return { accessToken: data.accessToken, refreshToken: data.refreshToken }; -} - -export async function apiLogout(refreshToken: string): Promise { - await authFetch('/auth/logout', { refreshToken }); -} diff --git a/apps/trails/lib/trailSearch.ts b/apps/trails/lib/trailSearch.ts index d6f84351a8..410f55f71d 100644 --- a/apps/trails/lib/trailSearch.ts +++ b/apps/trails/lib/trailSearch.ts @@ -1,6 +1,9 @@ -import { authedFetch } from 'trails-app/lib/apiFetch'; +import { asStringRecord } from '@packrat/guards'; +import { AuthExpiredError, apiClient } from 'trails-app/lib/apiClient'; import type { TrailSummaryWithCoords } from 'trails-app/lib/overpass'; +export { AuthExpiredError } from 'trails-app/lib/apiClient'; + export interface TrailSearchParams { q?: string; lat?: number; @@ -16,6 +19,10 @@ export interface TrailSearchResult { hasMore: boolean; } +interface ApiBbox { + coordinates?: number[][][][]; +} + interface ApiTrail { osmId: string; name: string | null; @@ -24,15 +31,13 @@ interface ApiTrail { distance: string | null; difficulty: string | null; description: string | null; - bbox: { coordinates?: number[][][][] } | null; + bbox: ApiBbox | null; } function bboxCenter(bbox: ApiTrail['bbox']): [number, number] | null { - // bbox is GeoJSON Feature (ST_AsGeoJSON(ST_Envelope(geometry))) — extract centroid if (!bbox?.coordinates?.[0]) return null; const ring = bbox.coordinates[0]; if (!ring) return null; - // ring is [[minLon, minLat], [maxLon, minLat], [maxLon, maxLat], [minLon, maxLat], [minLon, minLat]] const lons = ring.flatMap((p) => (typeof p[0] === 'number' ? [p[0]] : [])); const lats = ring.flatMap((p) => (typeof p[1] === 'number' ? [p[1]] : [])); if (lons.length === 0 || lats.length === 0) return null; @@ -44,25 +49,25 @@ function bboxCenter(bbox: ApiTrail['bbox']): [number, number] | null { } export async function searchTrails(params: TrailSearchParams): Promise { - const qs = new URLSearchParams(); - if (params.q) qs.set('q', params.q); - if (params.lat !== undefined) qs.set('lat', String(params.lat)); - if (params.lon !== undefined) qs.set('lon', String(params.lon)); - if (params.radius !== undefined) qs.set('radius', String(params.radius)); - if (params.sport) qs.set('sport', params.sport); - qs.set('limit', String(params.limit ?? 20)); - if (params.offset) qs.set('offset', String(params.offset)); + const { data, error, status } = await apiClient.trails.search.get({ + query: { + q: params.q, + lat: params.lat, + lon: params.lon, + radius: params.radius, + sport: params.sport, + limit: params.limit ?? 20, + offset: params.offset, + }, + }); - const res = await authedFetch(`/api/trails/search?${qs.toString()}`); - - if (!res.ok) { - const body = (await res.json().catch(() => ({}))) as { message?: string }; - throw new Error(body.message ?? `Search failed: ${res.status}`); + if (status === 401) throw new AuthExpiredError(); + if (error || !data) { + const msg = asStringRecord(error?.value)['message']; + throw new Error(msg ?? `Search failed: ${status}`); } - const data = (await res.json()) as { trails: ApiTrail[]; hasMore: boolean }; - - const trails: TrailSummaryWithCoords[] = data.trails.map((t) => ({ + const trails: TrailSummaryWithCoords[] = (data.trails as ApiTrail[]).map((t) => ({ osmId: t.osmId, name: t.name, sport: t.sport, @@ -70,9 +75,9 @@ export async function searchTrails(params: TrailSearchParams): Promise; + register(email: string, opts: { password: string; firstName?: string }): Promise; verifyEmail(otp: string): Promise; resendVerification(): Promise; login(email: string, password: string): Promise; @@ -37,6 +35,11 @@ interface AuthActions { const AuthContext = createContext<(AuthState & AuthActions) | null>(null); +function apiError(error: unknown, fallback: string): Error { + const msg = asStringRecord(error).message; + return new Error(msg ?? fallback); +} + export function AuthProvider({ children }: { children: React.ReactNode }) { const [state, setState] = useState({ isAuthed: false, @@ -55,8 +58,13 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { }, []); const register = useCallback( - async (email: string, opts: { password: string; username: string }) => { - await apiRegister({ email, password: opts.password, username: opts.username }); + async (email: string, opts: { password: string; firstName?: string }) => { + const { error, status } = await apiClient.auth.register.post({ + email, + password: opts.password, + firstName: opts.firstName, + }); + if (error) throw apiError(error.value, `Registration failed: ${status}`); setState((s) => ({ ...s, pendingEmail: email })); }, [], @@ -65,10 +73,20 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const verifyEmail = useCallback( async (otp: string) => { if (!state.pendingEmail) throw new Error('No pending email verification'); - const { accessToken, refreshToken, user } = await apiVerifyEmail(state.pendingEmail, otp); + const { data, error, status } = await apiClient.auth['verify-email'].post({ + email: state.pendingEmail, + code: otp, + }); + if (error || !data) throw apiError(error?.value, `Verification failed: ${status}`); + const { accessToken, refreshToken, user } = data; + if (!accessToken || !refreshToken || !user) { + throw new Error('Verification failed: missing token data'); + } + const parsedUser = fromZod(UserInfoSchema)(user); + if (!parsedUser) throw new Error('Verification failed: unexpected user shape'); setTokens(accessToken, refreshToken); - setUser(user); - setState({ isAuthed: true, user, pendingEmail: null }); + setUser(parsedUser); + setState({ isAuthed: true, user: parsedUser, pendingEmail: null }); setAuthGateOpen(false); }, [state.pendingEmail], @@ -76,14 +94,24 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const resendVerification = useCallback(async () => { if (!state.pendingEmail) throw new Error('No pending email'); - await apiResendVerification(state.pendingEmail); + const { error, status } = await apiClient.auth['resend-verification'].post({ + email: state.pendingEmail, + }); + if (error) throw apiError(error.value, `Resend failed: ${status}`); }, [state.pendingEmail]); const login = useCallback(async (email: string, password: string) => { - const { accessToken, refreshToken, user } = await apiLogin(email, password); + const { data, error, status } = await apiClient.auth.login.post({ email, password }); + if (error || !data) throw apiError(error?.value, `Login failed: ${status}`); + const { accessToken, refreshToken, user } = data; + if (!accessToken || !refreshToken || !user) { + throw new Error('Login failed: missing token data'); + } + const parsedUser = fromZod(UserInfoSchema)(user); + if (!parsedUser) throw new Error('Login failed: unexpected user shape'); setTokens(accessToken, refreshToken); - setUser(user); - setState({ isAuthed: true, user, pendingEmail: null }); + setUser(parsedUser); + setState({ isAuthed: true, user: parsedUser, pendingEmail: null }); setAuthGateOpen(false); }, []); @@ -91,7 +119,7 @@ export function AuthProvider({ children }: { children: React.ReactNode }) { const refreshToken = getRefreshToken(); if (refreshToken) { try { - await apiLogout(refreshToken); + await apiClient.auth.logout.post({ refreshToken }); } catch { // ignore — clear tokens regardless } diff --git a/apps/trails/package.json b/apps/trails/package.json index 92d7de1ca4..17c5c0fcc0 100644 --- a/apps/trails/package.json +++ b/apps/trails/package.json @@ -11,6 +11,7 @@ "start": "next start" }, "dependencies": { + "@packrat/api-client": "workspace:*", "@packrat/guards": "workspace:*", "@packrat/overpass": "workspace:*", "@packrat/web-ui": "workspace:*", diff --git a/bun.lock b/bun.lock index 694a937692..1563b1d569 100644 --- a/bun.lock +++ b/bun.lock @@ -342,6 +342,7 @@ "name": "packrat-trails-app", "version": "2.0.24", "dependencies": { + "@packrat/api-client": "workspace:*", "@packrat/guards": "workspace:*", "@packrat/overpass": "workspace:*", "@packrat/web-ui": "workspace:*", From 8825db0b12e728b883debcd0251b0d740a858deb Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 08:12:10 -0600 Subject: [PATCH 07/21] =?UTF-8?q?fix(trails):=20safe-cast=20annotation=20o?= =?UTF-8?q?n=20Treaty=E2=86=92ApiTrail=20narrowing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/trails/lib/trailSearch.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/trails/lib/trailSearch.ts b/apps/trails/lib/trailSearch.ts index 410f55f71d..fcf990c931 100644 --- a/apps/trails/lib/trailSearch.ts +++ b/apps/trails/lib/trailSearch.ts @@ -63,10 +63,11 @@ export async function searchTrails(params: TrailSearchParams): Promise ({ osmId: t.osmId, name: t.name, From 1a3c9005279893a9fcff2580506fe7d658ef387d Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 09:52:56 -0600 Subject: [PATCH 08/21] fix(catalog): use double-cast to satisfy TS2352 in treaty response casts Direct `as CatalogItem[]` rejected because Treaty's inferred type doesn't sufficiently overlap the hand-written interface; `as unknown as` is the standard escape hatch when shapes are known to match at runtime. --- .../catalog/components/CatalogBrowserModal.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/expo/features/catalog/components/CatalogBrowserModal.tsx b/apps/expo/features/catalog/components/CatalogBrowserModal.tsx index 38a40684fe..ac74276f04 100644 --- a/apps/expo/features/catalog/components/CatalogBrowserModal.tsx +++ b/apps/expo/features/catalog/components/CatalogBrowserModal.tsx @@ -165,7 +165,7 @@ export function CatalogBrowserModal({ const { data: popularData, isLoading: isPopularLoading } = usePopularCatalogItems(8); // safe-cast: treaty response shape matches CatalogItem[] as validated by the API schema - const popularItems = (popularData?.items ?? []) as CatalogItem[]; + const popularItems = (popularData?.items ?? []) as unknown as CatalogItem[]; const { data: paginatedData, @@ -188,12 +188,11 @@ export function CatalogBrowserModal({ error: searchError, } = useVectorSearch({ query: debouncedSearchValue, limit: 20 }); + const rawItems = isSearching + ? searchResult?.items || [] + : paginatedData?.pages.flatMap((page) => page.items) || []; // safe-cast: treaty response shape matches CatalogItem[] as validated by the API schema - const items = ( - isSearching - ? searchResult?.items || [] - : paginatedData?.pages.flatMap((page) => page.items) || [] - ) as CatalogItem[]; // safe-cast: treaty response shape matches CatalogItem[] + const items = rawItems as unknown as CatalogItem[]; const isLoading = isSearching ? isSearchLoading : isPaginatedLoading; const error = isSearching ? searchError : paginatedError; From 74e5114cad95093300a0da0e57b1b8ab0daf0e12 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 10:13:36 -0600 Subject: [PATCH 09/21] refactor(catalog): derive CatalogItem from CatalogItemSchema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the hand-written CatalogItem interface with z.infer so the type flows directly from the API schema — no more divergence, no more casts. - Add usageCount to CatalogItemSchema (computed from packItems in detail route) - Parse useVectorSearch and useCatalogItemDetails responses through their schemas - Remove all as CatalogItem[] casts from CatalogBrowserModal and CatalogItemsScreen - Widen PackItemInput.description to string | null to satisfy intersection type - Convert null→undefined at the assignment boundary in useCreatePackItem --- .../components/CatalogBrowserModal.tsx | 7 +- .../catalog/components/ItemReviews.tsx | 2 +- .../catalog/hooks/useCatalogItemDetails.ts | 3 +- .../features/catalog/hooks/useVectorSearch.ts | 3 +- .../catalog/screens/CatalogItemsScreen.tsx | 11 +- apps/expo/features/catalog/types.ts | 107 ++---------------- .../components/AddPackTemplateItemActions.tsx | 7 +- .../packs/components/AddPackItemActions.tsx | 10 +- .../features/packs/hooks/useCreatePackItem.ts | 2 +- apps/expo/features/packs/input.ts | 2 +- packages/api/src/schemas/catalog.ts | 1 + 11 files changed, 29 insertions(+), 126 deletions(-) diff --git a/apps/expo/features/catalog/components/CatalogBrowserModal.tsx b/apps/expo/features/catalog/components/CatalogBrowserModal.tsx index ac74276f04..78b36465a0 100644 --- a/apps/expo/features/catalog/components/CatalogBrowserModal.tsx +++ b/apps/expo/features/catalog/components/CatalogBrowserModal.tsx @@ -164,8 +164,7 @@ export function CatalogBrowserModal({ const { recentItems } = useRecentlyUsedCatalogItems(); const { data: popularData, isLoading: isPopularLoading } = usePopularCatalogItems(8); - // safe-cast: treaty response shape matches CatalogItem[] as validated by the API schema - const popularItems = (popularData?.items ?? []) as unknown as CatalogItem[]; + const popularItems = popularData?.items ?? []; const { data: paginatedData, @@ -188,11 +187,9 @@ export function CatalogBrowserModal({ error: searchError, } = useVectorSearch({ query: debouncedSearchValue, limit: 20 }); - const rawItems = isSearching + const items = isSearching ? searchResult?.items || [] : paginatedData?.pages.flatMap((page) => page.items) || []; - // safe-cast: treaty response shape matches CatalogItem[] as validated by the API schema - const items = rawItems as unknown as CatalogItem[]; const isLoading = isSearching ? isSearchLoading : isPaginatedLoading; const error = isSearching ? searchError : paginatedError; diff --git a/apps/expo/features/catalog/components/ItemReviews.tsx b/apps/expo/features/catalog/components/ItemReviews.tsx index 37e023f35d..ca3b205968 100644 --- a/apps/expo/features/catalog/components/ItemReviews.tsx +++ b/apps/expo/features/catalog/components/ItemReviews.tsx @@ -26,7 +26,7 @@ export function ItemReviews({ reviews }: ItemReviewsProps) { })); }; - const formatDate = (dateString: string) => { + const formatDate = (dateString: Date | string) => { const date = new Date(dateString); return date.toLocaleDateString('en-US', { year: 'numeric', diff --git a/apps/expo/features/catalog/hooks/useCatalogItemDetails.ts b/apps/expo/features/catalog/hooks/useCatalogItemDetails.ts index 1881874c01..f672865154 100644 --- a/apps/expo/features/catalog/hooks/useCatalogItemDetails.ts +++ b/apps/expo/features/catalog/hooks/useCatalogItemDetails.ts @@ -1,3 +1,4 @@ +import { CatalogItemSchema } from '@packrat/api/schemas/catalog'; import { useQuery } from '@tanstack/react-query'; import { apiClient } from 'expo-app/lib/api/packrat'; import { useAuthenticatedQueryToolkit } from 'expo-app/lib/hooks/useAuthenticatedQueryToolkit'; @@ -5,7 +6,7 @@ import { useAuthenticatedQueryToolkit } from 'expo-app/lib/hooks/useAuthenticate export const getCatalogItem = async (id: string) => { const { data, error } = await apiClient.catalog({ id }).get(); if (error) throw new Error(`Failed to fetch catalog item: ${error.value}`); - return data; + return CatalogItemSchema.parse(data); }; export function useCatalogItemDetails(id: string) { diff --git a/apps/expo/features/catalog/hooks/useVectorSearch.ts b/apps/expo/features/catalog/hooks/useVectorSearch.ts index 70680e8f7b..34baec182b 100644 --- a/apps/expo/features/catalog/hooks/useVectorSearch.ts +++ b/apps/expo/features/catalog/hooks/useVectorSearch.ts @@ -1,3 +1,4 @@ +import { VectorSearchResponseSchema } from '@packrat/api/schemas/catalog'; import { useQuery } from '@tanstack/react-query'; import { apiClient } from 'expo-app/lib/api/packrat'; import { useAuthenticatedQueryToolkit } from 'expo-app/lib/hooks/useAuthenticatedQueryToolkit'; @@ -13,7 +14,7 @@ const vectorSearchApi = async (query: string, limit?: number) => { }, }); if (error) throw new Error(`Vector search API error: ${error.value}`); - return data; + return VectorSearchResponseSchema.parse(data); }; export const useVectorSearch = ({ query, limit }: { query: string; limit?: number }) => { diff --git a/apps/expo/features/catalog/screens/CatalogItemsScreen.tsx b/apps/expo/features/catalog/screens/CatalogItemsScreen.tsx index e7a3dc89a9..dacebd4c18 100644 --- a/apps/expo/features/catalog/screens/CatalogItemsScreen.tsx +++ b/apps/expo/features/catalog/screens/CatalogItemsScreen.tsx @@ -67,14 +67,11 @@ function CatalogItemsScreen() { isLoading: isVectorLoading, error: vectorError, } = useVectorSearch({ query: trimmedQuery, limit: 10 }); - // safe-cast: treaty response shape matches CatalogItem[] as validated by the API schema - const searchResults: CatalogItem[] = (vectorResult?.items ?? []) as unknown as CatalogItem[]; + const searchResults = vectorResult?.items ?? []; - const paginatedItems: CatalogItem[] = - // safe-cast: treaty response shape matches CatalogItem[] as validated by the API schema - ((paginatedData?.pages.flatMap((page) => page.items) ?? []) as CatalogItem[]).filter((item) => - Boolean(item?.id), - ); + const paginatedItems = (paginatedData?.pages.flatMap((page) => page.items) ?? []).filter((item) => + Boolean(item?.id), + ); const totalItems = paginatedData?.pages[0]?.totalCount ?? 0; diff --git a/apps/expo/features/catalog/types.ts b/apps/expo/features/catalog/types.ts index 019359c72f..a00dbdf8e8 100644 --- a/apps/expo/features/catalog/types.ts +++ b/apps/expo/features/catalog/types.ts @@ -1,99 +1,8 @@ -import type { WeightUnit } from 'expo-app/types'; +import type { CatalogItemSchema } from '@packrat/api/schemas/catalog'; +import type { z } from 'zod'; import type { PackItemInput } from '../packs/input'; -export interface CatalogItemLink { - id: string; - title: string; - url: string; - type: 'official' | 'review' | 'guide' | 'purchase' | 'other'; -} - -export interface CatalogItemReview { - id: string; - userId: string; - userName: string; - userAvatar: string; - rating: number; - text: string; - date: string; - helpful: number; - verified: boolean; -} - -export interface CatalogItem { - id: number; - name: string; - productUrl: string; - sku: string; - weight: number; - weightUnit: WeightUnit; - description?: string | null; - categories?: string[] | null; - images?: string[] | null; - brand?: string | null; - model?: string | null; - ratingValue?: number | null; - color?: string | null; - size?: string | null; - price?: number | null; - availability?: 'in_stock' | 'out_of_stock' | 'preorder' | null; - seller?: string | null; - productSku?: string | null; - material?: string | null; - currency?: string | null; - condition?: string | null; - reviewCount?: number | null; - usageCount?: number | null; - - variants?: Array<{ - attribute: string; - values: string[]; - }> | null; - - techs?: Record | null; - - links?: Array<{ - title: string; - url: string; - }> | null; - - reviews?: Array<{ - user_name: string; - user_avatar?: string | null; - context?: Record | null; - recommends?: boolean | null; - rating: number; - title: string; - text: string; - date: string; - images?: string[] | null; - upvotes?: number | null; - downvotes?: number | null; - verified?: boolean | null; - }> | null; - - qas?: Array<{ - question: string; - user?: string | null; - date: string; - answers: Array<{ - a: string; - date: string; - user?: string | null; - upvotes?: number | null; - }>; - }> | null; - - faqs?: Array<{ - question: string; - answer: string; - }> | null; - - embedding?: number[] | null; // vector(1536) - - createdAt: string; - updatedAt: string; -} +export type CatalogItem = z.infer; export type CatalogItemWithQuantity = CatalogItem & { quantity?: number }; @@ -130,8 +39,14 @@ export interface CatalogItemInput { currency?: string; condition?: string; techs?: Record; - links?: CatalogItemLink[]; - reviews?: CatalogItemReview[]; + links?: Array<{ title: string; url: string }>; + reviews?: Array<{ + user_name: string; + rating: number; + title: string; + text: string; + date: string; + }>; } export type CatalogItemWithPackItemFields = CatalogItem & Partial; diff --git a/apps/expo/features/pack-templates/components/AddPackTemplateItemActions.tsx b/apps/expo/features/pack-templates/components/AddPackTemplateItemActions.tsx index e056c88c17..6700665c15 100644 --- a/apps/expo/features/pack-templates/components/AddPackTemplateItemActions.tsx +++ b/apps/expo/features/pack-templates/components/AddPackTemplateItemActions.tsx @@ -1,7 +1,7 @@ import { useActionSheet } from '@expo/react-native-action-sheet'; import type { BottomSheetModal } from '@gorhom/bottom-sheet'; import { BottomSheetView } from '@gorhom/bottom-sheet'; -import { isFunction, nullToUndefined } from '@packrat/guards'; +import { isFunction } from '@packrat/guards'; import { Sheet, Text, useColorScheme } from '@packrat/ui/nativewindui'; import * as Burnt from 'burnt'; import { appAlert } from 'expo-app/app/_layout'; @@ -120,10 +120,7 @@ export default React.forwardRef { trackRecentlyUsed(catalogItems); - await addItemsToPackTemplate( - packTemplateId, - catalogItems.map((item) => ({ ...item, description: nullToUndefined(item.description) })), - ); + await addItemsToPackTemplate(packTemplateId, catalogItems); const itemWord = catalogItems.length === 1 ? t('packTemplates.item') : t('packTemplates.items'); Burnt.toast({ diff --git a/apps/expo/features/packs/components/AddPackItemActions.tsx b/apps/expo/features/packs/components/AddPackItemActions.tsx index df417ff07a..ebaccfa5c4 100644 --- a/apps/expo/features/packs/components/AddPackItemActions.tsx +++ b/apps/expo/features/packs/components/AddPackItemActions.tsx @@ -1,7 +1,7 @@ import { useActionSheet } from '@expo/react-native-action-sheet'; import type { BottomSheetModal } from '@gorhom/bottom-sheet'; import { BottomSheetView } from '@gorhom/bottom-sheet'; -import { isFunction, nullToUndefined } from '@packrat/guards'; +import { isFunction } from '@packrat/guards'; import { Sheet, Text, useColorScheme } from '@packrat/ui/nativewindui'; import { Icon } from 'expo-app/components/Icon'; import { isAuthed } from 'expo-app/features/auth/store'; @@ -109,13 +109,7 @@ export default React.forwardRef( if (catalogItems.length > 0) { trackRecentlyUsed(catalogItems); try { - await addItemsToPack( - packId, - catalogItems.map((item) => ({ - ...item, - description: nullToUndefined(item.description), - })), - ); + await addItemsToPack(packId, catalogItems); } catch (error) { console.error('Error adding catalog items to pack:', error); Alert.alert(t('common.error'), t('catalog.somethingWentWrong')); diff --git a/apps/expo/features/packs/hooks/useCreatePackItem.ts b/apps/expo/features/packs/hooks/useCreatePackItem.ts index a9d677971b..164c2af32d 100644 --- a/apps/expo/features/packs/hooks/useCreatePackItem.ts +++ b/apps/expo/features/packs/hooks/useCreatePackItem.ts @@ -13,7 +13,7 @@ export function useCreatePackItem() { const newItem: PackItem = { id, name: itemData.name, - description: itemData.description, + description: itemData.description ?? undefined, weight: itemData.weight, weightUnit: itemData.weightUnit, quantity: itemData.quantity, diff --git a/apps/expo/features/packs/input.ts b/apps/expo/features/packs/input.ts index 8d9a4895e0..806351be95 100644 --- a/apps/expo/features/packs/input.ts +++ b/apps/expo/features/packs/input.ts @@ -2,7 +2,7 @@ import type { WeightUnit } from 'expo-app/types'; export interface PackItemInput { name: string; - description?: string; + description?: string | null; weight: number; weightUnit: WeightUnit; quantity: number; diff --git a/packages/api/src/schemas/catalog.ts b/packages/api/src/schemas/catalog.ts index 1efda868b0..e6477c6b7c 100644 --- a/packages/api/src/schemas/catalog.ts +++ b/packages/api/src/schemas/catalog.ts @@ -94,6 +94,7 @@ export const CatalogItemSchema = z.object({ ) .nullable() .optional(), + usageCount: z.number().int().optional(), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), }); From 29294c66fcd9da8718d62eb5aad021cc9be480d2 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 11:28:40 -0600 Subject: [PATCH 10/21] refactor(auth): derive User type from UserSchema and parse at auth boundaries Replace hand-written User interface (which diverged from the API schema on firstName/lastName/role nullability and had a preferredWeightUnit field never returned by any endpoint) with z.infer & { preferredWeightUnit? }. Replace the four `data.user as unknown as User` casts in useAuthActions with UserSchema.parse(), giving runtime validation of the API response shape at every sign-in and email-verify path. --- apps/expo/features/auth/hooks/useAuthActions.ts | 14 +++++--------- apps/expo/features/profile/types.ts | 14 +++++--------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/apps/expo/features/auth/hooks/useAuthActions.ts b/apps/expo/features/auth/hooks/useAuthActions.ts index d7892300c1..3c98a99f43 100644 --- a/apps/expo/features/auth/hooks/useAuthActions.ts +++ b/apps/expo/features/auth/hooks/useAuthActions.ts @@ -1,3 +1,4 @@ +import { UserSchema } from '@packrat/api/schemas/users'; import { isObject } from '@packrat/guards'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { @@ -6,7 +7,6 @@ import { statusCodes, } from '@react-native-google-signin/google-signin'; import { userStore } from 'expo-app/features/auth/store'; -import type { User } from 'expo-app/features/profile/types'; import { apiClient } from 'expo-app/lib/api/packrat'; import { t } from 'expo-app/lib/i18n'; import ImageCacheManager from 'expo-app/lib/utils/ImageCacheManager'; @@ -69,8 +69,7 @@ export function useAuthActions() { await setToken(data.accessToken); await setRefreshToken(data.refreshToken); - // safe-cast: Treaty response type differs from local User type; Zod-validated at API boundary - userStore.set(data.user as unknown as User); + userStore.set(UserSchema.parse(data.user)); setNeedsReauth(false); redirect(redirectTo); @@ -101,8 +100,7 @@ export function useAuthActions() { await setToken(data.accessToken); await setRefreshToken(data.refreshToken); - // safe-cast: Treaty response type differs from local User type; Zod-validated at API boundary - userStore.set(data.user as unknown as User); + userStore.set(UserSchema.parse(data.user)); setNeedsReauth(false); redirect(redirectTo); @@ -149,8 +147,7 @@ export function useAuthActions() { await setToken(data.accessToken); await setRefreshToken(data.refreshToken); - // safe-cast: Treaty response type differs from local User type; Zod-validated at API boundary - userStore.set(data.user as unknown as User); + userStore.set(UserSchema.parse(data.user)); setNeedsReauth(false); redirect(redirectTo); @@ -257,8 +254,7 @@ export function useAuthActions() { await Storage.setItem('refresh_token', data.refreshToken); await setToken(data.accessToken); await setRefreshToken(data.refreshToken); - // safe-cast: Treaty response type differs from local User type; Zod-validated at API boundary - userStore.set(data.user as unknown as User); + userStore.set(UserSchema.parse(data.user)); redirect(redirectTo); } diff --git a/apps/expo/features/profile/types.ts b/apps/expo/features/profile/types.ts index f487d58a38..433004df47 100644 --- a/apps/expo/features/profile/types.ts +++ b/apps/expo/features/profile/types.ts @@ -1,11 +1,7 @@ +import type { UserSchema } from '@packrat/api/schemas/users'; +import type { z } from 'zod'; import type { WeightUnit } from '../packs/types'; -export interface User { - id: number; - email: string; - firstName: string; - lastName: string; - avatarUrl?: string | null; - role: 'USER' | 'ADMIN'; - preferredWeightUnit: WeightUnit; -} +export type User = z.infer & { + preferredWeightUnit?: WeightUnit; +}; From 57bbf099c12b5194c5024964f0e378037ce42b61 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 13:06:04 -0600 Subject: [PATCH 11/21] fix(trails): align @types/react version with monorepo (~19.1.10) --- apps/trails/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/trails/package.json b/apps/trails/package.json index 17c5c0fcc0..7f8dad959d 100644 --- a/apps/trails/package.json +++ b/apps/trails/package.json @@ -37,7 +37,7 @@ "@cloudflare/workers-types": "^4.20250405.0", "@types/leaflet": "^1.9.21", "@types/node": "^25.6.0", - "@types/react": "~19.2.10", + "@types/react": "~19.1.10", "@types/react-dom": "^19.1.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", From c6256d7f7a7232e5ff4843524613097b5bda2e6a Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 14:33:00 -0600 Subject: [PATCH 12/21] chore: update bun.lock for apps/trails deps (@types/leaflet) --- bun.lock | 435 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 234 insertions(+), 201 deletions(-) diff --git a/bun.lock b/bun.lock index 1563b1d569..a09fbdd042 100644 --- a/bun.lock +++ b/bun.lock @@ -368,7 +368,7 @@ "@cloudflare/workers-types": "^4.20250405.0", "@types/leaflet": "^1.9.21", "@types/node": "^25.6.0", - "@types/react": "~19.2.10", + "@types/react": "~19.1.10", "@types/react-dom": "^19.1.6", "postcss": "^8.5.6", "postcss-import": "^16.1.1", @@ -556,6 +556,9 @@ "packages/units": { "name": "@packrat/units", "version": "0.1.0", + "dependencies": { + "@packrat/guards": "workspace:*", + }, "devDependencies": { "convert-units": "3.0.0-beta.8", "vitest": "~3.1.4", @@ -680,19 +683,19 @@ "packages": { "@0no-co/graphql.web": ["@0no-co/graphql.web@1.2.0", "", { "peerDependencies": { "graphql": "^14.0.0 || ^15.0.0 || ^16.0.0" }, "optionalPeers": ["graphql"] }, "sha512-/1iHy9TTr63gE1YcR5idjx8UREz1s0kFhydf3bBLCXyqjhkIc6igAzTOx3zPifCwFR87tsh/4Pa9cNts6d2otw=="], - "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.104", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZKX5n74io8VIRlhIMSLWVlvT3sXC8Z7cZ9GHuWBWZDVi96+62AIsWuLGvMfcBA1STYuSoDrp6rIziZmvrTq0TA=="], + "@ai-sdk/gateway": ["@ai-sdk/gateway@3.0.111", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@vercel/oidc": "3.2.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-gzdRuEH9Mqeuu8zG6j4of3EH3fFJUI0UIubyeaA8gep6KzhCJF7uaTfagSE7x2vLAf381g/NrxsXhhH7Hon9iA=="], - "@ai-sdk/google": ["@ai-sdk/google@3.0.64", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CbR82EgGPNrj/6q0HtclwuCqe0/pDShyv3nWDP/A9DroujzWXnLMlUJVrgPOsg4b40zQCwwVs2XSKCxvt/4QaA=="], + "@ai-sdk/google": ["@ai-sdk/google@3.0.70", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ZM77Ri/hWCf7hTt8KmdwI8CU92RSy58MCK3kr6sj+okuU7g8RZfm6JNeiyYdMxKkUhrOOC8WmbnLMfQaSO/QoQ=="], - "@ai-sdk/openai": ["@ai-sdk/openai@3.0.53", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-Wld+Rbc05KaUn08uBt06eEuwcgalcIFtIl32Yp+GxuZXUQwOb6YeAuq+C6da4ch6BurFoqEaLemJVwjBb7x+PQ=="], + "@ai-sdk/openai": ["@ai-sdk/openai@3.0.63", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-4yY/m8a57MNNVoJCsXuNblKf6BO4yuAuLKRX4tzSNffBEBSp1FlcWdPE0Z4FkqUeS0AJhYSSqp0GIiA/cIcDNA=="], - "@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.29", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-9UfV7ywpnxNLPI/hdheFPHXDdLG9vLqNoPSdRTPV+nPAX117zMtBmqD5KSvmXTjeF7IXpObUZ9bWzwMR/ewL1g=="], + "@ai-sdk/perplexity": ["@ai-sdk/perplexity@3.0.33", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-aNt6pTAzq+akadDXVdg2SjN2dODtaVlkKbw8/35c+sekr+Tx0sJwVqMR1udxrjLzhQvz8qtfsWRuz+hB9pmOnQ=="], - "@ai-sdk/provider": ["@ai-sdk/provider@3.0.8", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-oGMAgGoQdBXbZqNG0Ze56CHjDZ1IDYOwGYxYjO5KLSlz5HiNQ9udIXsPZ61VWaHGZ5XW/jyjmr6t2xz2jGVwbQ=="], + "@ai-sdk/provider": ["@ai-sdk/provider@3.0.10", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-Q3BZ27qfpYqnCYGvE3vt+Qi6LGOF9R5Nmzn+9JoM1lCRsD9mYaIhfJLkSunN48nfGXJ6n+XNV0J/XVpqGQl7Dw=="], - "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.23", "", { "dependencies": { "@ai-sdk/provider": "3.0.8", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-z8GlDaCmRSDlqkMF2f4/RFgWxdarvIbyuk+m6WXT1LYgsnGiXRJGTD2Z1+SDl3LqtFuRtGX1aghYvQLoHL/9pg=="], + "@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.27", "", { "dependencies": { "@ai-sdk/provider": "3.0.10", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-ubkAJ+xODouwtmN1tYlvTPphH1hPOBfZaEQe8U7skGvFAnIRs9PPpsq57bC2+Ky/MB4yzhd6YOsxTAx9sGpazw=="], - "@ai-sdk/react": ["@ai-sdk/react@3.0.170", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.23", "ai": "6.0.168", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-YUDn+mK0c8iUz14rCBf1A0zg6SV5b5aSVUz+azF1bdBd1SFXVI19dKYR+PQSpZY+0+z+zs252AAsacUqiO98Kw=="], + "@ai-sdk/react": ["@ai-sdk/react@3.0.178", "", { "dependencies": { "@ai-sdk/provider-utils": "4.0.27", "ai": "6.0.176", "swr": "^2.2.5", "throttleit": "2.1.0" }, "peerDependencies": { "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1" } }, "sha512-fLkT2fD8Bgi8nsp16PKL6UOiAIwZiwCfv+jXRhOikx0nIh3AdtaGUqJ1yqig7BXg5F9oxHmiaD7YpLC2fN/arQ=="], "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], @@ -780,7 +783,7 @@ "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], - "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], + "@babel/compat-data": ["@babel/compat-data@7.29.3", "", {}, "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg=="], "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], @@ -790,7 +793,7 @@ "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], - "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.28.6", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.28.6", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow=="], + "@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.3", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", "@babel/traverse": "^7.29.0", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA=="], "@babel/helper-create-regexp-features-plugin": ["@babel/helper-create-regexp-features-plugin@7.28.5", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw=="], @@ -826,7 +829,7 @@ "@babel/highlight": ["@babel/highlight@7.25.9", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.25.9", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw=="], - "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + "@babel/parser": ["@babel/parser@7.29.3", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA=="], "@babel/plugin-proposal-decorators": ["@babel/plugin-proposal-decorators@7.29.0", "", { "dependencies": { "@babel/helper-create-class-features-plugin": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/plugin-syntax-decorators": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA=="], @@ -994,23 +997,23 @@ "@cloudflare/containers": ["@cloudflare/containers@0.0.30", "", {}, "sha512-i148xBgmyn/pje82ZIyuTr/Ae0BT/YWwa1/GTJcw6DxEjUHAzZLaBCiX446U9OeuJ2rBh/L/9FIzxX5iYNt1AQ=="], - "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.4.2", "", {}, "sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ=="], + "@cloudflare/kv-asset-handler": ["@cloudflare/kv-asset-handler@0.5.0", "", {}, "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg=="], "@cloudflare/unenv-preset": ["@cloudflare/unenv-preset@2.16.1", "", { "peerDependencies": { "unenv": "2.0.0-rc.24", "workerd": ">1.20260305.0 <2.0.0-0" }, "optionalPeers": ["workerd"] }, "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw=="], "@cloudflare/vitest-pool-workers": ["@cloudflare/vitest-pool-workers@0.8.71", "", { "dependencies": { "birpc": "0.2.14", "cjs-module-lexer": "^1.2.3", "devalue": "^5.3.2", "miniflare": "4.20250906.0", "semver": "^7.7.1", "wrangler": "4.35.0", "zod": "^3.22.3" }, "peerDependencies": { "@vitest/runner": "2.0.x - 3.2.x", "@vitest/snapshot": "2.0.x - 3.2.x", "vitest": "2.0.x - 3.2.x" } }, "sha512-keu2HCLQfRNwbmLBCDXJgCFpANTaYnQpE01fBOo4CNwiWHUT7SZGN7w64RKiSWRHyYppStXBuE5Ng7F42+flpg=="], - "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260424.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-yFR1XaJbSDLg/qbwtrYaU2xwFXatIPKR5nrMQCN1q/m6+Qe/j6r+kCnFEvOJjMZOm9iCKsE6Qly5clgl4u32qw=="], + "@cloudflare/workerd-darwin-64": ["@cloudflare/workerd-darwin-64@1.20260507.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-S85aMwcaPJUjKWDiG6iMMnioKWtPLACa6m0j/EhHR1GYfVpnxb974cBc6d25L+sf7jHWHJI2u5hGp0UTJ7MtXQ=="], - "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260424.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LqWKcE7x/9KyC2iQvKPeb20hKST3dYXDZlYTvFymgR1DfLS0OFOCzVGTloVNd7WqvK4SkdzBYfxo7QMIAeBK0w=="], + "@cloudflare/workerd-darwin-arm64": ["@cloudflare/workerd-darwin-arm64@1.20260507.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-GMEBu8Zp9Q97HLnf7bWJN4KjWpN5MxpeqdvHjBGWNl8UYprJI0k+Jkp89+Wh5S8vIon+HoVbDfOzPa7VwgL6Eg=="], - "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260424.1", "", { "os": "linux", "cpu": "x64" }, "sha512-YlEBFbAYZHe/ylzl8WEYQEU/jr+0XMqXaST2oBk5oVjksdb1NGuJaggluCdZAzuJJ8UqdTmyhY5u/qrasbiFWA=="], + "@cloudflare/workerd-linux-64": ["@cloudflare/workerd-linux-64@1.20260507.1", "", { "os": "linux", "cpu": "x64" }, "sha512-QlrKEBdgA3uVc0Ok0Q3+0/CW0CTjgj5ySir1i1YY5FXVv0X6GpwtnB5umjunjF2MFprss+L+iFGZzxcSvMC1nA=="], - "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260424.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-qJ0X0m6cL8fWDUPDg8K4IxYZXNJI6XbeOihqjnqKbAClrjdPDn8VUSd+z2XiCQ5NylMtMrpa/skC9UfaR6mh8g=="], + "@cloudflare/workerd-linux-arm64": ["@cloudflare/workerd-linux-arm64@1.20260507.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-eGbbupEtK2nh9V9Dhcx3vv3GTKeXqSVNgAEYVCCN0NGS9tl9HbMoHRX/4JL181FKXROMigWBCQVL//qPhsAzBQ=="], - "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260424.1", "", { "os": "win32", "cpu": "x64" }, "sha512-tZ7Z9qmYNAP6z1/+8r/zKbk8F8DZmpmwNzMeN+zkde2Wnhfr3FBqOkJXT/5zmli8HPoWrIXxSiyqcNDMy8V2Zg=="], + "@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20260507.1", "", { "os": "win32", "cpu": "x64" }, "sha512-dmClJ/E0BAcuDetQIZFqbeAXejWrG5pysGRMQ6T83Y0IW/7IAamY2zFEkAJ10I5xwZsdHuYsZtzlOxpEXpJs7A=="], - "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260425.1", "", {}, "sha512-f6dlo3SsA+TNqjveavPDN73nxRfCOOd0iMdf8iEosgR/RJtQlrGwfr5L5Vf7x/5cpeeguxScKevuaMmdjpOECw=="], + "@cloudflare/workers-types": ["@cloudflare/workers-types@4.20260508.1", "", {}, "sha512-0KNR+UkrYJYmtyQ5tOjUT/wt/U34FuE4Y8FLSbPMwFrGQQpmvR9wKghwRkL4d28y5t9jI9cvGqeAoo/cerTnCQ=="], "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], @@ -1138,9 +1141,9 @@ "@expo/fingerprint": ["@expo/fingerprint@0.15.5", "", { "dependencies": { "@expo/spawn-async": "^1.7.2", "arg": "^5.0.2", "chalk": "^4.1.2", "debug": "^4.3.4", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "minimatch": "^10.2.2", "p-limit": "^3.1.0", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "bin": { "fingerprint": "bin/cli.js" } }, "sha512-mdVoAMcux1WlM6kd1RoWiHRNqKqS+J6mKmWQ/BKgeh937S/fcW58EE68O6nc4KDXtWi3PBeNHskOFcgyIuD4hw=="], - "@expo/image-utils": ["@expo/image-utils@0.8.13", "", { "dependencies": { "@expo/require-utils": "^55.0.4", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "semver": "^7.6.0" } }, "sha512-1I//yBQeTY6p0u1ihqGNDAr35EbSG8uFEupFrIF0jd++h9EWH33521yZJU1yE+mwGlzCb61g3ehu78siMhXBlA=="], + "@expo/image-utils": ["@expo/image-utils@0.8.14", "", { "dependencies": { "@expo/require-utils": "^55.0.5", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "getenv": "^2.0.0", "jimp-compact": "0.16.1", "parse-png": "^2.1.0", "semver": "^7.6.0" } }, "sha512-5Sn+jG4Cw+shC2wDMXoqSAJnvERbiwzHn05FpWtD5IBflfTIs5gUmjzwiGVyjOdlMSQhgRrw/AymPbmO9h9mpQ=="], - "@expo/json-file": ["@expo/json-file@10.0.13", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-pX/XjQn7tgNw6zuuV2ikmegmwe/S7uiwhrs2wXrANMkq7ozrA+JcZwgW9Q/8WZgciBzfAhNp5hnackHcrmapQA=="], + "@expo/json-file": ["@expo/json-file@10.0.14", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "json5": "^2.2.3" } }, "sha512-yWwBFywFv+SxkJp/pIzzA416JVYflNUh7pqQzgaA6nXDqRyK7KfrqVzk8PdUfDnqbBcaZZxpzNssfQZzp5KHrA=="], "@expo/metro": ["@expo/metro@54.2.0", "", { "dependencies": { "metro": "0.83.3", "metro-babel-transformer": "0.83.3", "metro-cache": "0.83.3", "metro-cache-key": "0.83.3", "metro-config": "0.83.3", "metro-core": "0.83.3", "metro-file-map": "0.83.3", "metro-minify-terser": "0.83.3", "metro-resolver": "0.83.3", "metro-runtime": "0.83.3", "metro-source-map": "0.83.3", "metro-symbolicate": "0.83.3", "metro-transform-plugins": "0.83.3", "metro-transform-worker": "0.83.3" } }, "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w=="], @@ -1148,9 +1151,9 @@ "@expo/metro-runtime": ["@expo/metro-runtime@6.1.2", "", { "dependencies": { "anser": "^1.4.9", "pretty-format": "^29.7.0", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-dom": "*", "react-native": "*" }, "optionalPeers": ["react-dom"] }, "sha512-nvM+Qv45QH7pmYvP8JB1G8JpScrWND3KrMA6ZKe62cwwNiX/BjHU28Ear0v/4bQWXlOY0mv6B8CDIm8JxXde9g=="], - "@expo/osascript": ["@expo/osascript@2.4.2", "", { "dependencies": { "@expo/spawn-async": "^1.7.2" } }, "sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw=="], + "@expo/osascript": ["@expo/osascript@2.4.3", "", { "dependencies": { "@expo/spawn-async": "^1.7.2" } }, "sha512-wbuj3EebM7W9hN/Wp4xTzKd6rQ2zKJzAxkFxkOOwyysLp0HOAgQ4/5RINyoS241pZUX2rUHq7mAJ7pcCQ8U0Ow=="], - "@expo/package-manager": ["@expo/package-manager@1.10.4", "", { "dependencies": { "@expo/json-file": "^10.0.13", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-y9Mr4Kmpk4abAVZrNNPCdzOZr8nLLyi18p1SXr0RCVA8IfzqZX/eY4H+50a0HTmXqIsPZrQdcdb4I3ekMS9GvQ=="], + "@expo/package-manager": ["@expo/package-manager@1.10.5", "", { "dependencies": { "@expo/json-file": "^10.0.14", "@expo/spawn-async": "^1.7.2", "chalk": "^4.0.0", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "resolve-workspace-root": "^2.0.0" } }, "sha512-nCP9Mebfl3jvOr0/P6VAuyah6PAtun+aihIL2zAtuE8uSe94JWkVZ7051i0MUVO+y3gFpBqnr8IIH5ch+VJjHA=="], "@expo/plist": ["@expo/plist@0.4.8", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.2.3", "xmlbuilder": "^15.1.1" } }, "sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ=="], @@ -1158,7 +1161,7 @@ "@expo/react-native-action-sheet": ["@expo/react-native-action-sheet@4.1.1", "", { "dependencies": { "@types/hoist-non-react-statics": "^3.3.1", "hoist-non-react-statics": "^3.3.0" }, "peerDependencies": { "react": ">=18.0.0" } }, "sha512-4KRaba2vhqDRR7ObBj6nrD5uJw8ePoNHdIOMETTpgGTX7StUbrF4j/sfrP1YUyaPEa1P8FXdwG6pB+2WtrJd1A=="], - "@expo/require-utils": ["@expo/require-utils@55.0.4", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0" }, "optionalPeers": ["typescript"] }, "sha512-JAANvXqV7MOysWeVWgaiDzikoyDjJWOV/ulOW60Zb3kXJfrx2oZOtGtDXDFKD1mXuahQgoM5QOjuZhF7gFRNjA=="], + "@expo/require-utils": ["@expo/require-utils@55.0.5", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0" }, "optionalPeers": ["typescript"] }, "sha512-U4K/CQ2VpXuwfNGsN+daKmYOt15hCP8v/pXaYH6eut7kdYZo6SfJ1yr67BIcJ+1Gzzs+QzTxswAZChKpXmceyw=="], "@expo/schema-utils": ["@expo/schema-utils@0.1.8", "", {}, "sha512-9I6ZqvnAvKKDiO+ZF8BpQQFYWXOJvTAL5L/227RUbWG1OVZDInFifzCBiqAZ3b67NRfeAgpgvbA7rejsqhY62A=="], @@ -1172,7 +1175,7 @@ "@expo/ws-tunnel": ["@expo/ws-tunnel@1.0.6", "", {}, "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q=="], - "@expo/xcpretty": ["@expo/xcpretty@4.4.3", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, "sha512-wC562eD3gS6vO2tWHToFhlFnmHKfKHgF1oyvojeSkLK/ZYop1bMU+7cOMiF9Sq70CzcsLy/EMRy/uRc76QmNRw=="], + "@expo/xcpretty": ["@expo/xcpretty@4.4.4", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "chalk": "^4.1.0", "js-yaml": "^4.1.0" }, "bin": { "excpretty": "build/cli.js" } }, "sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw=="], "@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="], @@ -1182,7 +1185,7 @@ "@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="], - "@gorhom/bottom-sheet": ["@gorhom/bottom-sheet@5.2.10", "", { "dependencies": { "@gorhom/portal": "1.0.14", "invariant": "^2.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-native": "*", "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.16.1", "react-native-reanimated": ">=3.16.0 || >=4.0.0-" }, "optionalPeers": ["@types/react", "@types/react-native"] }, "sha512-MnFddmVOlaoash0d9g1ClqFqX+32h/sV3PNEFz9A8XCvUbZGQM9OG6HHAzTb+eQfUGA8DkaurI+wfpNFyzj5Yw=="], + "@gorhom/bottom-sheet": ["@gorhom/bottom-sheet@5.2.13", "", { "dependencies": { "@gorhom/portal": "1.0.14", "invariant": "^2.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-native": "*", "react": "*", "react-native": "*", "react-native-gesture-handler": ">=2.16.1", "react-native-reanimated": ">=3.16.0 || >=4.0.0-" }, "optionalPeers": ["@types/react", "@types/react-native"] }, "sha512-cMxyd9kIowMME9kw2wwXAuWrXUQnPkJQz7rDbOSBBomZ+PpV/C/tlO1UozBrAe2zs3tp9th3JMW21FI/y0VeuQ=="], "@gorhom/portal": ["@gorhom/portal@1.0.14", "", { "dependencies": { "nanoid": "^3.3.1" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-MXyL4xvCjmgaORr/rtryDNFy3kU4qUbKlwtQqqsygd0xX3mhKjOLn6mQK8wfu0RkoE0pBE0nAasRoHua+/QZ7A=="], @@ -1280,7 +1283,7 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@legendapp/state": ["@legendapp/state@3.0.0-beta.46", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "expo-sqlite": "^15.0.0" }, "optionalPeers": ["expo-sqlite"] }, "sha512-TcCabsE9jPW2r0sKQbUet46L0hbWiupKoun9UUkcHyF/6Jec1RyJCmLrdgFPnYZ9HwupJKIRxJVlxNrg2tG3SQ=="], + "@legendapp/state": ["@legendapp/state@3.0.0-beta.47", "", { "dependencies": { "use-sync-external-store": "^1.2.2" }, "peerDependencies": { "expo-sqlite": "^15.0.0" }, "optionalPeers": ["expo-sqlite"] }, "sha512-MPgPacXXSoAazAv7ulW/o0ZAtK4YHk3twvXZ241l2HqAHciHozb7tg5SMbEAc2HKUUfC3JBh+9+DXfMsYokLpQ=="], "@manypkg/cli": ["@manypkg/cli@0.24.0", "", { "dependencies": { "@manypkg/get-packages": "^3.0.0", "detect-indent": "^7.0.1", "normalize-path": "^3.0.0", "p-limit": "^6.2.0", "package-json": "^10.0.1", "parse-github-url": "^1.0.3", "picocolors": "^1.1.1", "sembear": "^0.7.0", "semver": "^7.7.1", "tinyexec": "^1.0.1", "validate-npm-package-name": "^6.0.0" }, "bin": { "manypkg": "bin.js" } }, "sha512-O1vbx4TnwaeeDXlNaa+N0LIKg3JmI2gEG8JaGn97UuXgiXJIYlAhfepJTykICV0i0oQHvb0xNfNmvYhwJ/cGgA=="], @@ -1298,23 +1301,23 @@ "@neondatabase/serverless": ["@neondatabase/serverless@1.1.0", "", {}, "sha512-r3ZZhRjEcfEdKIZnoB1RusNgvHuaBRqfCzV4Gi+5A9yUX0S4HTws/ASWqt13wL4y4I+0rqsWGdA2w7EQXHi3+Q=="], - "@next/env": ["@next/env@15.5.15", "", {}, "sha512-vcmyu5/MyFzN7CdqRHO3uHO44p/QPCZkuTUXroeUmhNP8bL5PHFEhik22JUazt+CDDoD6EpBYRCaS2pISL+/hg=="], + "@next/env": ["@next/env@15.5.18", "", {}, "sha512-hAV85Ckd9QR6RvH04MEKwsfLTksvFpO47j9xwtoIuvuPnlwecpSi+uZTtm8HirVbtlI2Fnz//xpcSTjFdyJk+g=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.15", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6PvFO2Tzt10GFK2Ro9tAVEtacMqRmTarYMFKAnV2vYMdwWc73xzmDQyAV7SwEdMhzmiRoo7+m88DuiXlJlGeaw=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@15.5.18", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w0WvQf1n+txiwns/9pwIQteCJpZTbxzO2SE0FLcwuD4v0WEh1JPOjdyxWL21XwJsdpx8cFRjyzxzCS/siP7HcQ=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.15", "", { "os": "darwin", "cpu": "x64" }, "sha512-G+YNV+z6FDZTp/+IdGyIMFqalBTaQSnvAA+X/hrt+eaTRFSznRMz9K7rTmzvM6tDmKegNtyzgufZW0HwVzEqaQ=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@15.5.18", "", { "os": "darwin", "cpu": "x64" }, "sha512-znn71QmDuxm+BOaglihMZfvyySMnNljkVIY5Z2TCssBmm+WqL6c19VhtH5ktFkHa8EZ2bnTUpcNcmNSQsg67og=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-eVkrMcVIBqGfXB+QUC7jjZ94Z6uX/dNStbQFabewAnk13Uy18Igd1YZ/GtPRzdhtm7QwC0e6o7zOQecul4iC1w=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-yPPe5MNL+igZUa+OsqQJisqSfh6oarIuA1Q0BDxljGJhRQyZeP+WRHh7rs/jZUGMh5aY0YdIjXZG0VohkKkUdw=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.15", "", { "os": "linux", "cpu": "arm64" }, "sha512-RwSHKMQ7InLy5GfkY2/n5PcFycKA08qI1VST78n09nN36nUPqCvGSMiLXlfUmzmpQpF6XeBYP2KRWHi0UW3uNg=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@15.5.18", "", { "os": "linux", "cpu": "arm64" }, "sha512-glaCczEWIrHsokFZ3pP08U4BpKxwIdnT+txdOM32OBgpL9Yw4aqx8NejmgtZQZOdstQ5f0L3CasIZudzCuD+nw=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.15", "", { "os": "linux", "cpu": "x64" }, "sha512-nplqvY86LakS+eeiuWsNWvfmK8pFcOEW7ZtVRt4QH70lL+0x6LG/m1OpJ/tvrbwjmR8HH9/fH2jzW1GlL03TIg=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-oUfg2EgJmU3R0OCOWiokGFUTvZiPfXtriXiuF3YNxRoROCdgvTedHIzYoeKH34gsZxS/V7mHbfq2hpAHwhH1/A=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.15", "", { "os": "linux", "cpu": "x64" }, "sha512-eAgl9NKQ84/sww0v81DQINl/vL2IBxD7sMybd0cWRw6wqgouVI53brVRBrggqBRP/NWeIAE1dm5cbKYoiMlqDQ=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@15.5.18", "", { "os": "linux", "cpu": "x64" }, "sha512-JLxSP3KTd9iu/bvUMQxH7RJo9xKSHf55/6RPE4a6FTSZygGn7uvZbCej0AHXydwkggQGSD9UddSjwv6Xz5ESfA=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.15", "", { "os": "win32", "cpu": "arm64" }, "sha512-GJVZC86lzSquh0MtvZT+L7G8+jMnJcldloOjA8Kf3wXvBrvb6OGe2MzPuALxFshSm/IpwUtD2mIoof39ymf52A=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@15.5.18", "", { "os": "win32", "cpu": "arm64" }, "sha512-ir1v7enP52K2HNz3tQQvwF+x7VNxBk1ciiZ18WBPvxf4C59IqdfmHPJYK3vH7rSxpuCVw/8C712wTXNAtEp+NA=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.15", "", { "os": "win32", "cpu": "x64" }, "sha512-nFucjVdwlFqxh/JG3hWSJ4p8+YJV7Ii8aPDuBQULB6DzUF4UNZETXLfEUk+oI2zEznWWULPt7MeuTE6xtK1HSA=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@15.5.18", "", { "os": "win32", "cpu": "x64" }, "sha512-LIu5me6QTANCd25E7I5uIEfvgQ06RK7tvHAbYo3zCb3VpxQEPvMcSpd87NwUABDT6MbGPdEGR5VRiK4PPTJhQg=="], "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], @@ -1324,9 +1327,9 @@ "@opentelemetry/api": ["@opentelemetry/api@1.9.0", "", {}, "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg=="], - "@oxc-project/types": ["@oxc-project/types@0.127.0", "", {}, "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ=="], + "@oxc-project/types": ["@oxc-project/types@0.129.0", "", {}, "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg=="], - "@packrat-ai/nativewindui": ["@packrat-ai/nativewindui@2.0.3", "https://npm.pkg.github.com/download/@packrat-ai/nativewindui/2.0.3/6d1d9364d32c4a145cc9fc49a73744b553db5e14", { "peerDependencies": { "@expo/vector-icons": ">=15.0.0", "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "^8.4.0", "@react-native-community/slider": "^5.0.0", "@react-native-picker/picker": "^2.11.0", "@react-native-segmented-control/segmented-control": "^2.5.0", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.0.4", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@shopify/flash-list": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo-blur": "~15.0.8", "expo-device": "~8.0.0", "expo-glass-effect": "*", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linear-gradient": "~15.0.8", "expo-navigation-bar": "~5.0.10", "expo-router": "~6.0.23", "expo-symbols": "~1.0.8", "nativewind": "^4.2.3", "react": ">=19.0.0", "react-native": ">=0.79.0", "react-native-keyboard-controller": "^1.16.7", "react-native-reanimated": ">=3.17.0", "react-native-safe-area-context": ">=5.4.0", "react-native-screens": ">=4.11.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-C0PzEmKNqc7KS6DcxRN6hOtyIpkK6xGLO0I3x6eKBtunGSPseQWz/JEdsQW2i42xRVZN3r83loYb3v7dVUDTFg=="], + "@packrat-ai/nativewindui": ["@packrat-ai/nativewindui@2.0.6", "https://npm.pkg.github.com/download/@packrat-ai/nativewindui/2.0.6/555a865d3d9f1ca8a3ccf1318c26286d7b2f522c", { "peerDependencies": { "@expo/vector-icons": ">=15.0.0", "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "^8.4.0", "@react-native-community/slider": "^5.0.0", "@react-native-picker/picker": "^2.11.0", "@react-native-segmented-control/segmented-control": "^2.5.0", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.0.4", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@shopify/flash-list": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo-blur": "~55.0.0", "expo-device": "~55.0.0", "expo-glass-effect": "~55.0.0", "expo-haptics": "~55.0.0", "expo-image": "~55.0.0", "expo-linear-gradient": "~55.0.0", "expo-navigation-bar": "~55.0.0", "expo-router": "~55.0.0", "expo-symbols": "~55.0.0", "nativewind": "^4.2.3", "react": ">=19.2.0", "react-native": ">=0.83.0", "react-native-keyboard-controller": "^1.21.0", "react-native-reanimated": ">=4.2.0", "react-native-safe-area-context": ">=5.6.0", "react-native-screens": ">=4.23.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-AB8MfYtVajR8i1MyQUeeJ7QuoHpkeGPqKjmv4Gu5+FZRDM1LNqtf3YTfuFEEoOx7UJc7/5tEWsDo8hOeOQBzCg=="], "@packrat/analytics": ["@packrat/analytics@workspace:packages/analytics"], @@ -1480,6 +1483,8 @@ "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], + "@react-leaflet/core": ["@react-leaflet/core@3.0.0", "", { "peerDependencies": { "leaflet": "^1.9.0", "react": "^19.0.0", "react-dom": "^19.0.0" } }, "sha512-3EWmekh4Nz+pGcr+xjf0KNyYfC3U2JjnkWsh0zcqaexYqmmB5ZhH37kz41JXGmKzpaMZCnPofBBm64i+YrEvGQ=="], + "@react-native-ai/apple": ["@react-native-ai/apple@0.10.0", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.10", "zod": "^4.0.0" }, "peerDependencies": { "react-native": ">=0.76.0" } }, "sha512-VhtMvzsDnaiU9FLBAstJUYIkQgy/Ce0ll6a/tkx0/uzQF0cChDluft0hXF3/w0p5ZOGIGNrZicvjIiVjrmoA1w=="], "@react-native-ai/llama": ["@react-native-ai/llama@0.10.0", "", { "dependencies": { "@ai-sdk/provider": "^2.0.0", "@ai-sdk/provider-utils": "^3.0.10", "react-native-blob-util": "^0.24.5", "zod": "^4.0.0" }, "peerDependencies": { "llama.rn": "^0.10.0-rc.0", "react-native": ">=0.76.0" } }, "sha512-BlRd+G5xoA/9mpyOLTAUIYtS3tJ3GkTo5z64qJ4jR76f0YpTjz7V2Ky1wHop9GBZWA5dJRke0Yj/EG4J5TsIQg=="], @@ -1518,19 +1523,19 @@ "@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.81.5", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "*", "react-native": "*" }, "optionalPeers": ["@types/react"] }, "sha512-UVXgV/db25OPIvwZySeToXD/9sKKhOdkcWmmf4Jh8iBZuyfML+/5CasaZ1E7Lqg6g3uqVQq75NqIwkYmORJMPw=="], - "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.15.10", "", { "dependencies": { "@react-navigation/elements": "^2.9.15", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-Ao/yYlrpr0cwYYGxt9FDMQk+tTSHNm4WTaszyhroINLdoEMuKH19k1tGFdYbRBKHJx1UIH8kD+EZTYW1w6LL3Q=="], + "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.15.12", "", { "dependencies": { "@react-navigation/elements": "^2.9.16", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.2.3", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-Kp7oUEWgUB3NLBbgPkE8DGPtHU6jfhqPQGhFlUYYJ+PeoFcRX++Y1GMn90yYanCKpob8I7l6/YbzhN39owO06Q=="], - "@react-navigation/core": ["@react-navigation/core@7.17.2", "", { "dependencies": { "@react-navigation/routers": "^7.5.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA=="], + "@react-navigation/core": ["@react-navigation/core@7.17.3", "", { "dependencies": { "@react-navigation/routers": "^7.5.4", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-cFOzT4d6oOjdAWwk69onVQXhEN1CHmGau5zCP5DO9mLeO/N1Db0a/ZXP57fn0t/6lf7OPX8vl6tPcv3lBR4F/Q=="], - "@react-navigation/drawer": ["@react-navigation/drawer@7.9.9", "", { "dependencies": { "@react-navigation/elements": "^2.9.15", "color": "^4.2.3", "react-native-drawer-layout": "^4.2.2", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-ZeHhx5MH7Y/qG+28KU0PDtBjNcNnpvnafPwIoSzSrN8M55HvtQex90TP3ylmHtErhw2RDWlp30vpmWvG0wvFIA=="], + "@react-navigation/drawer": ["@react-navigation/drawer@7.9.10", "", { "dependencies": { "@react-navigation/elements": "^2.9.16", "color": "^4.2.3", "react-native-drawer-layout": "^4.2.2", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "@react-navigation/native": "^7.2.3", "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-reanimated": ">= 2.0.0", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-BO5Hi61KBOW5fSuDFtx8YYJ8Dwdl5ErAgqrfRw2o/SvAD5TrI1Nh2AmG9leQ6UfiU2k8L4WqVq5gk348zhanmA=="], - "@react-navigation/elements": ["@react-navigation/elements@2.9.15", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-cyz/pPiyyC6gaTVLsGFc1g0MYgrmuCFqklAWGXMWPscr5YU3ui94vPI4vnZwcsEy0T758TQWLzmS5XudZeRKcA=="], + "@react-navigation/elements": ["@react-navigation/elements@2.9.16", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.2.3", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-uScoLXOvQwdj7w9hn69kyubNYm7EZMAX9fAqbrTIA8mYUAv+9qfhJxOcO8VXcoT0Vm8EKNDXqg5n5WNxcdN0Ww=="], - "@react-navigation/native": ["@react-navigation/native@7.2.2", "", { "dependencies": { "@react-navigation/core": "^7.17.2", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w=="], + "@react-navigation/native": ["@react-navigation/native@7.2.3", "", { "dependencies": { "@react-navigation/core": "^7.17.3", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-Q6vENZJnrRUmNzPa8m/SINzV0IQ2ndEQvVHQaJ0M1TvtyB8OWO/3hCl3ukWvnRUakroFNgwYokBXUaRhVvqU6g=="], - "@react-navigation/native-stack": ["@react-navigation/native-stack@7.14.12", "", { "dependencies": { "@react-navigation/elements": "^2.9.15", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.2.2", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-dUfpkrVeVKKV8iqXsmoUp3Rv0iH3YaB3eZwScru/FlcqAp/r3/qA6zEXkGX9hZK+/ziWAPFrf1frBSNbgOYSFQ=="], + "@react-navigation/native-stack": ["@react-navigation/native-stack@7.14.13", "", { "dependencies": { "@react-navigation/elements": "^2.9.16", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.2.3", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-o6hNgvwUiKZFIFQI+27YndmtSRxgJXFAJDwkBhmNeD8EEdJUxom2NDKzqFPjwsDYQIRYXJmIHR3Qz2cRsGwSYg=="], - "@react-navigation/routers": ["@react-navigation/routers@7.5.3", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg=="], + "@react-navigation/routers": ["@react-navigation/routers@7.5.4", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-5ONLNA3hKwAo3n95ENaZvWHkLeC8+7dgy8U/D+mO0Tvrih21nfxGNRqizI+qN2gxryWvYRk/pq5NsnTw6TtZbg=="], "@reduxjs/toolkit": ["@reduxjs/toolkit@2.11.2", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "@standard-schema/utils": "^0.3.0", "immer": "^11.0.0", "redux": "^5.0.1", "redux-thunk": "^3.1.0", "reselect": "^5.1.0" }, "peerDependencies": { "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" }, "optionalPeers": ["react", "react-redux"] }, "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ=="], @@ -1554,89 +1559,89 @@ "@rn-primitives/utils": ["@rn-primitives/utils@1.4.0", "", { "peerDependencies": { "react": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native", "react-native-web"] }, "sha512-nMFZ99AGKakMRDAlfbsYUfqwKO0LItWtp58YTwxmNuGVhXG43/zIfyWWaB3FJeOL+hhcpUn0YR7C1Vsrg0FgvQ=="], - "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-rc.17", "", { "os": "android", "cpu": "arm64" }, "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ=="], + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0", "", { "os": "android", "cpu": "arm64" }, "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA=="], - "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw=="], + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew=="], - "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-rc.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw=="], + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ=="], - "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-rc.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw=="], + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ=="], - "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm" }, "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ=="], + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0", "", { "os": "linux", "cpu": "arm" }, "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A=="], - "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q=="], + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ=="], - "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg=="], + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA=="], - "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA=="], + "@rolldown/binding-linux-ppc64-gnu": ["@rolldown/binding-linux-ppc64-gnu@1.0.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg=="], - "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "s390x" }, "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA=="], + "@rolldown/binding-linux-s390x-gnu": ["@rolldown/binding-linux-s390x-gnu@1.0.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA=="], - "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA=="], + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA=="], - "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-rc.17", "", { "os": "linux", "cpu": "x64" }, "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw=="], + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0", "", { "os": "linux", "cpu": "x64" }, "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw=="], - "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-rc.17", "", { "os": "none", "cpu": "arm64" }, "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA=="], + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0", "", { "os": "none", "cpu": "arm64" }, "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig=="], - "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-rc.17", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA=="], + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0", "", { "dependencies": { "@emnapi/core": "1.10.0", "@emnapi/runtime": "1.10.0", "@napi-rs/wasm-runtime": "^1.1.4" }, "cpu": "none" }, "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg=="], - "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA=="], + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow=="], - "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-rc.17", "", { "os": "win32", "cpu": "x64" }, "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg=="], + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0", "", { "os": "win32", "cpu": "x64" }, "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg=="], "@rolldown/plugin-babel": ["@rolldown/plugin-babel@0.2.3", "", { "dependencies": { "picomatch": "^4.0.4" }, "peerDependencies": { "@babel/core": "^7.29.0 || ^8.0.0-rc.1", "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1", "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1", "rolldown": "^1.0.0-rc.5", "vite": "^8.0.0" }, "optionalPeers": ["@babel/plugin-transform-runtime", "@babel/runtime", "vite"] }, "sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw=="], - "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.17", "", {}, "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg=="], + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0", "", {}, "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.2", "", { "os": "android", "cpu": "arm" }, "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.3", "", { "os": "android", "cpu": "arm" }, "sha512-x35CNW/ANXG3hE/EZpRU8MXX1JDN86hBb2wMGAtltkz7pc6cxgjpy1OMMfDosOQ+2hWqIkag/fGok1Yady9nGw=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.2", "", { "os": "android", "cpu": "arm64" }, "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.3", "", { "os": "android", "cpu": "arm64" }, "sha512-xw3xtkDApIOGayehp2+Rz4zimfkaX65r4t47iy+ymQB2G4iJCBBfj0ogVg5jpvjpn8UWn/+q9tprxleYeNp3Hw=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-vo6Y5Qfpx7/5EaamIwi0WqW2+zfiusVihKatLvtN1VFVy3D13uERk/6gZLU1UiHRL6fDXqj/ELIeVRGnvcTE1g=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-D+0QGcZhBzTN82weOnsSlY7V7+RMmPuF1CkbxyMAGE8+ZHeUjyb76ZiWmBlCu//AQQONvxcqRbwZTajZKqjuOw=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.3", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-6HnvHCT7fDyj6R0Ph7A6x8dQS/S38MClRWeDLqc0MdfWkxjiu1HSDYrdPhqSILzjTIC/pnXbbJbo+ft+gy/9hQ=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.3", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KHLgC3WKlUYW3ShFKnnosZDOJ0xjg9zp7au3sIm2bs/tGBeC2ipmvRh/N7JKi0t9Ue20C0dpEshi8WUubg+cnA=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.2", "", { "os": "linux", "cpu": "arm" }, "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.3", "", { "os": "linux", "cpu": "arm" }, "sha512-DV6fJoxEYWJOvaZIsok7KrYl0tPvga5OZ2yvKHNNYyk/2roMLqQAbGhr78EQ5YhHpnhLKJD3S1WFusAkmUuV5g=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.2", "", { "os": "linux", "cpu": "arm" }, "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.3", "", { "os": "linux", "cpu": "arm" }, "sha512-mQKoJAzvuOs6F+TZybQO4GOTSMUu7v0WdxEk24krQ/uUxXoPTtHjuaUuPmFhtBcM4K0ons8nrE3JyhTuCFtT/w=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Whjj2qoiJ6+OOJMGptTYazaJvjOJm+iKHpXQM1P3LzGjt7Ff++Tp7nH4N8J/BUA7R9IHfDyx4DJIflifwnbmIA=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-4YTNHKqGng5+yiZt3mg77nmyuCfmNfX4fPmyUapBcIk+BdwSwmCWGXOUxhXbBEkFHtoN5boLj/5NON+u5QC9tg=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-SU3kNlhkpI4UqlUc2VXPGK9o886ZsSeGfMAX2ba2b8DKmMXq4AL7KUrkSWVbb7koVqx41Yczx6dx5PNargIrEA=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-6lDLl5h4TXpB1mTf2rQWnAk/LcXrx9vBfu/DT5TIPhvMhRWaZ5MxkIc8u4lJAmBo6klTe1ywXIUHFjylW505sg=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-BMo8bOw8evlup/8G+cj5xWtPyp93xPdyoSN16Zy90Q2QZ0ZYRhCt6ZJSwbrRzG9HApFabjwj2p25TUPDWrhzqQ=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-E0L8X1dZN1/Rph+5VPF6Xj2G7JJvMACVXtamTJIDrVI44Y3K+G8gQaMEAavbqCGTa16InptiVrX6eM6pmJ+7qA=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-oZJ/WHaVfHUiRAtmTAeo3DcevNsVvH8mbvodjZy7D5QKvCefO371SiKRpxoDcCxB3PTRTLayWBkvmDQKTcX/sw=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.3", "", { "os": "linux", "cpu": "none" }, "sha512-Dhbyh7j9FybM3YaTgaHmVALwA8AkUwTPccyCQ79TG9AJUsMQqgN1DDEZNr4+QUfwiWvLDumW5vdwzoeUF+TNxQ=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-cJd1X5XhHHlltkaypz1UcWLA8AcoIi1aWhsvaWDskD1oz2eKCypnqvTQ8ykMNI0RSmm7NkTdSqSSD7zM0xa6Ig=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.3", "", { "os": "linux", "cpu": "x64" }, "sha512-DAZDBHQfG2oQuhY7mc6I3/qB4LU2fQCjRvxbDwd/Jdvb9fypP4IJ4qmtu6lNjes6B531AI8cg1aKC2di97bUxA=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.2", "", { "os": "linux", "cpu": "x64" }, "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.3", "", { "os": "linux", "cpu": "x64" }, "sha512-cRxsE8c13mZOh3vP+wLDxpQBRrOHDIGOWyDL93Sy0Ga8y515fBcC2pjUfFwUe5T7tqvTvWbCpg1URM/AXdWIXA=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.3", "", { "os": "openbsd", "cpu": "x64" }, "sha512-QaWcIgRxqEdQdhJqW4DJctsH6HCmo5vHxY0krHSX4jMtOqfzC+dqDGuHM87bu4H8JBeibWx7jFz+h6/4C8wA5Q=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.2", "", { "os": "none", "cpu": "arm64" }, "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.3", "", { "os": "none", "cpu": "arm64" }, "sha512-AaXwSvUi3QIPtroAUw1t5yHGIyqKEXwH54WUocFolZhpGDruJcs8c+xPNDRn4XiQsS7MEwnYsHW2l0MBLDMkWg=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.3", "", { "os": "win32", "cpu": "arm64" }, "sha512-65LAKM/bAWDqKNEelHlcHvm2V+Vfb8C6INFxQXRHCvaVN1rJfwr4NvdP4FyzUaLqWfaCGaadf6UbTm8xJeYfEg=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.3", "", { "os": "win32", "cpu": "ia32" }, "sha512-EEM2gyhBF5MFnI6vMKdX1LAosE627RGBzIoGMdLloPZkXrUN0Ckqgr2Qi8+J3zip/8NVVro3/FjB+tjhZUgUHA=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.3", "", { "os": "win32", "cpu": "x64" }, "sha512-E5Eb5H/DpxaoXH++Qkv28RcUJboMopmdDUALBczvHMf7hNIxaDZqwY5lK12UK1BHacSmvupoEWGu+n993Z0y1A=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.3", "", { "os": "win32", "cpu": "x64" }, "sha512-hPt/bgL5cE+Qp+/TPHBqptcAgPzgj46mPcg/16zNUmbQk0j+mOEQV/+Lqu8QRtDV3Ek95Q6FeFITpuhl6OTsAA=="], "@ronradtke/react-native-markdown-display": ["@ronradtke/react-native-markdown-display@8.1.0", "", { "dependencies": { "css-to-react-native": "^3.2.0", "markdown-it": "^13.0.1", "prop-types": "^15.7.2", "react-native-fit-image": "^1.5.5" }, "peerDependencies": { "react": ">=16.2.0", "react-native": ">=0.50.4" } }, "sha512-pAtefWI76vpkxsEgIFivyq1q6ej8rDyR7oVM/cWAxUydyBej9LOvULjLAeFuFLbYAelHTNoYXmGxQOlFLBa0+w=="], @@ -1728,7 +1733,7 @@ "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.32", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/middleware-serde": "^4.2.20", "@smithy/node-config-provider": "^4.3.14", "@smithy/shared-ini-file-loader": "^4.4.9", "@smithy/types": "^4.14.1", "@smithy/url-parser": "^4.2.14", "@smithy/util-middleware": "^4.2.14", "tslib": "^2.6.2" } }, "sha512-ZZkgyjnJppiZbIm6Qbx92pbXYi1uzenIvGhBSCDlc7NwuAkiqSgS75j1czAD25ZLs2FjMjYy1q7gyRVWG6JA0Q=="], - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.5", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.3.0", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.4", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-wnYOpB5vATFKWrY2Z9Alb0KhjZI6AbzU6Fbz3Hq2GnURdRYWB4q+qWivQtSTwXcmWUA3MZ6krfwL6Cq5MAbxsA=="], + "@smithy/middleware-retry": ["@smithy/middleware-retry@4.5.7", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/node-config-provider": "^4.3.14", "@smithy/protocol-http": "^5.3.14", "@smithy/service-error-classification": "^4.3.1", "@smithy/smithy-client": "^4.12.13", "@smithy/types": "^4.14.1", "@smithy/util-middleware": "^4.2.14", "@smithy/util-retry": "^4.3.6", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-bRt6ZImqVSeTk39Nm81K20ObIiAZ3WefY7G6+iz/0tZjs4dgRRjvRX2sgsH+zi6iDCRR/aQvQofLKxxz4rPBZg=="], "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.20", "", { "dependencies": { "@smithy/core": "^3.23.17", "@smithy/protocol-http": "^5.3.14", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-Lx9JMO9vArPtiChE3wbEZ5akMIDQpWQtlu90lhACQmNOXcGXRbaDywMHDzuDZ2OkZzP+9wQfZi3YJT9F67zTQQ=="], @@ -1746,7 +1751,7 @@ "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-hr+YyqBD23GVvRxGGrcc/oOeNlK3PzT5Fu4dzrDXxzS1LpFiuL2PQQqKPs87M79aW7ziMs+nvB3qdw77SqE7Lw=="], - "@smithy/service-error-classification": ["@smithy/service-error-classification@4.3.0", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-9jKsBYQRPR0xBLgc2415RsA5PIcP2sis4oBdN9s0D13cg1B1284mNTjx9Yc+BEERXzuPm5ObktI96OxsKh8E9A=="], + "@smithy/service-error-classification": ["@smithy/service-error-classification@4.3.1", "", { "dependencies": { "@smithy/types": "^4.14.1" } }, "sha512-aUQuDGh760ts/8MU+APjIZhlLPKhIIfqyzZaJikLEIMrdxFvxuLYD0WxWzaYWpmLbQlXDe9p7EWM3HsBe0K6Gw=="], "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.9", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-495/V2I15SHgedSJoDPD23JuSfKAp726ZI1V0wtjB07Wh7q/0tri/0e0DLefZCHgxZonrGKt/OCTpAtP1wE1kQ=="], @@ -1778,7 +1783,7 @@ "@smithy/util-middleware": ["@smithy/util-middleware@4.2.14", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-1Su2vj9RYNDEv/V+2E+jXkkwGsgR7dc4sfHn9Z7ruzQHJIEni9zzw5CauvRXlFJfmgcqYP8fWa0dkh2Q2YaQyw=="], - "@smithy/util-retry": ["@smithy/util-retry@4.3.4", "", { "dependencies": { "@smithy/service-error-classification": "^4.3.0", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-FY1UQQ1VFmMwiYp1GVS4MeaGD5O0blLNYK0xCRHU+mJgeoH/hSY8Ld8sJWKQ6uznkh14HveRGQJncgPyNl9J+A=="], + "@smithy/util-retry": ["@smithy/util-retry@4.3.8", "", { "dependencies": { "@smithy/service-error-classification": "^4.3.1", "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-LUIxbTBi+OpvXpg91poGA6BdyoleMDLnfXjVDqyi2RvZmTveY5loE/FgYUBCR5LU2BThW2SoZRh8dTIIy38IPw=="], "@smithy/util-stream": ["@smithy/util-stream@4.5.25", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.17", "@smithy/node-http-handler": "^4.6.1", "@smithy/types": "^4.14.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-/PFpG4k8Ze8Ei+mMKj3oiPICYekthuzePZMgZbCqMiXIHHf4n2aZ4Ps0aSRShycFTGuj/J6XldmC0x0DwednIA=="], @@ -1786,7 +1791,7 @@ "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - "@smithy/util-waiter": ["@smithy/util-waiter@4.2.16", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-GtclrKoZ3Lt7jPQ7aTIYKfjY92OgceScftVnkTsG8e1KV8rkvZgN+ny6YSRhd9hxB8rZtwVbmln7NTvE5O3GmQ=="], + "@smithy/util-waiter": ["@smithy/util-waiter@4.3.0", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-JyjYmLAfS+pdxF92o4yLgEoy0zhayKTw73FU1aofLWwLcJw7iSqIY2exGmMTrl/lmZugP5p/zxdFSippJDfKWA=="], "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], @@ -1812,15 +1817,15 @@ "@tanstack/pacer-lite": ["@tanstack/pacer-lite@0.1.1", "", {}, "sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w=="], - "@tanstack/query-core": ["@tanstack/query-core@5.100.1", "", {}, "sha512-awvQhOO/2TrSCHE5LKKsXcvvj6WSBncwEcMFCB/ez0Qs0b17iyyivoGArNV3HFfXryZwCpnb/olsaBBKrIbtSw=="], + "@tanstack/query-core": ["@tanstack/query-core@5.100.9", "", {}, "sha512-SJSFw1S8+kQ0+knv/XGfrbocWoAlT7vDKsSImtLx3ZPQmEcR46hkDjLSvynSy25N8Ms4tIEini1FuBd5k7IscQ=="], - "@tanstack/query-devtools": ["@tanstack/query-devtools@5.100.1", "", {}, "sha512-jZLV2l7XjYxXCrXHj9pj15gZuY8Te+idoSPS2hIh3+SxOd20Gn0rfUoqEw9vc+us/b16hi0/DWqpzx9O1ZsyIQ=="], + "@tanstack/query-devtools": ["@tanstack/query-devtools@5.100.9", "", {}, "sha512-gqiptrTIhbK2PuCaPRHmWXfJG1NGYVFpAr0HqogEqiSBNB5xDz6fmesQt7w4WgMOqOQPnPHJ3ZDMuhDaXvNO8g=="], "@tanstack/react-form": ["@tanstack/react-form@1.29.1", "", { "dependencies": { "@tanstack/form-core": "1.29.1", "@tanstack/react-store": "^0.9.1" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-hVHk4g0phd0HxRsv2ry6Xt8BqmalT55Q3cokhJBCC1St0hcGZhgwJJbohm9atao45BPG9e55DGvtbwExqZe35g=="], - "@tanstack/react-query": ["@tanstack/react-query@5.100.1", "", { "dependencies": { "@tanstack/query-core": "5.100.1" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-UgWRLhQKprC37SsO6y1zRabOqDmM2gsdTNPbqTT35yl7kOOhwXU4nyfOiGHXPwoEFJV1IpSk85hjIFjNFWVpzw=="], + "@tanstack/react-query": ["@tanstack/react-query@5.100.9", "", { "dependencies": { "@tanstack/query-core": "5.100.9" }, "peerDependencies": { "react": "^18 || ^19" } }, "sha512-Oa44XkaI3kCNN6ME0KByU3xT3SEUNOMfZpHxL6+wFoTm+OeUFYHKdeYVe0aOXlRDm/f15sgLwEt2HDorIdW8+A=="], - "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.100.1", "", { "dependencies": { "@tanstack/query-devtools": "5.100.1" }, "peerDependencies": { "@tanstack/react-query": "^5.100.1", "react": "^18 || ^19" } }, "sha512-JuLinBUl/BlZhm0WVX83fJgE2a3YSbuEdxf3fgP+THg92hX7YfwuH5DzT35a6sL/rifZsPr0yJ9itB6jDOcdRg=="], + "@tanstack/react-query-devtools": ["@tanstack/react-query-devtools@5.100.9", "", { "dependencies": { "@tanstack/query-devtools": "5.100.9" }, "peerDependencies": { "@tanstack/react-query": "^5.100.9", "react": "^18 || ^19" } }, "sha512-mM3slaVGXJmz+pOLgXdANj75ikgQCyudyl3kmFvm6brI1JyVeY/+IeD17uDHIvZrD8hfoO2sdZ54RFsHdYAuhA=="], "@tanstack/react-store": ["@tanstack/react-store@0.9.3", "", { "dependencies": { "@tanstack/store": "0.9.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-y2iHd/N9OkoQbFJLUX1T9vbc2O9tjH0pQRgTcx1/Nz4IlwLvkgpuglXUx+mXt0g5ZDFrEeDnONPqkbfxXJKwRg=="], @@ -1830,7 +1835,7 @@ "@tokenizer/token": ["@tokenizer/token@0.3.0", "", {}, "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A=="], - "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.2", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -1862,7 +1867,7 @@ "@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="], - "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], "@types/fs-extra": ["@types/fs-extra@11.0.4", "", { "dependencies": { "@types/jsonfile": "*", "@types/node": "*" } }, "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ=="], @@ -1890,13 +1895,15 @@ "@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="], + "@types/leaflet": ["@types/leaflet@1.9.21", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-TbAd9DaPGSnzp6QvtYngntMZgcRk+igFELwR2N99XZn7RXUdKgsXMR+28bUO0rPsWp8MIu/f47luLIQuSLYv/w=="], + "@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="], "@types/mdx": ["@types/mdx@2.0.13", "", {}, "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw=="], "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + "@types/node": ["@types/node@25.6.2", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw=="], "@types/nodemailer": ["@types/nodemailer@6.4.23", "", { "dependencies": { "@types/node": "*" } }, "sha512-aFV3/NsYFLSx9mbb5gtirBSXJnAlrusoKNuPbxsASWc7vrKLmIrTQRpdcxNcSFL3VW2A2XpeLEavwb2qMi6nlQ=="], @@ -1924,11 +1931,11 @@ "@typescript-eslint/parser": ["@typescript-eslint/parser@7.18.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "7.18.0", "@typescript-eslint/types": "7.18.0", "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.0", "@typescript-eslint/types": "^8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Lw5ITrR5s5TbC19YSvlr63ZfLaJoU6vtKTHyB0GQOpX0W7d5/Ir6vUahWi/8Sps/nOukZQ0IB3SmlxZnjaKVnw=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.59.2", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.59.2", "@typescript-eslint/types": "^8.59.2", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw=="], "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "@typescript-eslint/visitor-keys": "7.18.0" } }, "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-91Sbl3s4Kb3SybliIY6muFBmHVv+pYXfybC4Oolp3dvk8BvIE3wOPc+403CWIT7mJNkfQRGtdqghzs2+Z91Tqg=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.59.2", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw=="], "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@7.18.0", "", { "dependencies": { "@typescript-eslint/typescript-estree": "7.18.0", "@typescript-eslint/utils": "7.18.0", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, "peerDependencies": { "eslint": "^8.56.0" } }, "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA=="], @@ -1940,7 +1947,7 @@ "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@7.18.0", "", { "dependencies": { "@typescript-eslint/types": "7.18.0", "eslint-visitor-keys": "^3.4.3" } }, "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg=="], - "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], + "@ungap/structured-clone": ["@ungap/structured-clone@1.3.1", "", {}, "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ=="], "@urql/core": ["@urql/core@5.2.0", "", { "dependencies": { "@0no-co/graphql.web": "^1.0.13", "wonka": "^6.3.2" } }, "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A=="], @@ -1978,9 +1985,9 @@ "agent-base": ["agent-base@7.1.4", "", {}, "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ=="], - "agents": ["agents@0.11.5", "", { "dependencies": { "@babel/plugin-proposal-decorators": "^7.29.0", "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/sdk": "1.29.0", "@rolldown/plugin-babel": "^0.2.3", "cron-schedule": "^6.0.0", "mimetext": "^3.0.28", "nanoid": "^5.1.9", "partyserver": "^0.4.1", "partysocket": "1.1.18", "yargs": "^18.0.0" }, "peerDependencies": { "@cloudflare/ai-chat": ">=0.0.8 <1.0.0", "@cloudflare/codemode": ">=0.0.7 <1.0.0", "@tanstack/ai": ">=0.10.2 <1.0.0", "@x402/core": "^2.0.0", "@x402/evm": "^2.0.0", "ai": "^6.0.0", "react": "^19.0.0", "vite": ">=6.0.0 <9.0.0", "zod": "^4.0.0" }, "optionalPeers": ["@cloudflare/ai-chat", "@cloudflare/codemode", "@tanstack/ai", "@x402/core", "@x402/evm", "vite"], "bin": { "agents": "dist/cli/index.js" } }, "sha512-1wPkA7OOfEdR4GKwaBmqdnZkOxutN2mCsolVU4ekg5QxrTLnC9Vz9LyZPcGqV2ldyfpUY7R73AUqtig5iYRLvQ=="], + "agents": ["agents@0.11.9", "", { "dependencies": { "@babel/plugin-proposal-decorators": "^7.29.0", "@cfworker/json-schema": "^4.1.1", "@modelcontextprotocol/sdk": "1.29.0", "@rolldown/plugin-babel": "^0.2.3", "cron-schedule": "^6.0.0", "mimetext": "^3.0.28", "nanoid": "^5.1.9", "partyserver": "^0.5.5", "partysocket": "1.1.18", "yargs": "^18.0.0" }, "peerDependencies": { "@cloudflare/ai-chat": ">=0.5.2 <1.0.0", "@cloudflare/codemode": ">=0.3.4 <1.0.0", "@tanstack/ai": ">=0.10.2 <1.0.0", "@x402/core": "^2.0.0", "@x402/evm": "^2.0.0", "ai": "^6.0.0", "react": "^19.0.0", "vite": ">=6.0.0 <9.0.0", "zod": "^4.0.0" }, "optionalPeers": ["@cloudflare/ai-chat", "@cloudflare/codemode", "@tanstack/ai", "@x402/core", "@x402/evm", "vite"], "bin": { "agents": "dist/cli/index.js" } }, "sha512-La8kXl/zEr9tu17Xc5BXb5Xz5yfrH+Oh98nnWtj1OxteO1AB0i2R26w77pXCT0ffViLaE3RtgN2dOq8QGDTwsA=="], - "ai": ["ai@6.0.168", "", { "dependencies": { "@ai-sdk/gateway": "3.0.104", "@ai-sdk/provider": "3.0.8", "@ai-sdk/provider-utils": "4.0.23", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-2HqCJuO+1V2aV7vfYs5LFEUfxbkGX+5oa54q/gCCTL7KLTdbxcCu5D7TdLA5kwsrs3Szgjah9q6D9tpjHM3hUQ=="], + "ai": ["ai@6.0.176", "", { "dependencies": { "@ai-sdk/gateway": "3.0.111", "@ai-sdk/provider": "3.0.10", "@ai-sdk/provider-utils": "4.0.27", "@opentelemetry/api": "1.9.0" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-dhxDef3VCIxaFr6tKyG0BrkkCelmnporlen8nHajIwCk7S4PvIaSVI/iyJenhFOZ9KBoKjCAoUs6TzZ3yrSjxw=="], "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], @@ -2074,7 +2081,7 @@ "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], - "baseline-browser-mapping": ["baseline-browser-mapping@2.10.21", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-Q+rUQ7Uz8AHM7DEaNdwvfFCTq7a43lNTzuS94eiWqwyxfV/wJv+oUivef51T91mmRY4d4A1u9rcSvkeufCVXlA=="], + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.27", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA=="], "bcryptjs": ["bcryptjs@3.0.3", "", { "bin": { "bcrypt": "bin/bcrypt" } }, "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g=="], @@ -2138,7 +2145,7 @@ "camelize": ["camelize@1.0.1", "", {}, "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="], - "caniuse-lite": ["caniuse-lite@1.0.30001790", "", {}, "sha512-bOoxfJPyYo+ds6W0YfptaCWbFnJYjh2Y1Eow5lRv+vI2u8ganPZqNm1JwNh0t2ELQCqIWg4B3dWEusgAmsoyOw=="], + "caniuse-lite": ["caniuse-lite@1.0.30001792", "", {}, "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], @@ -2332,7 +2339,7 @@ "detect-node-es": ["detect-node-es@1.1.0", "", {}, "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ=="], - "devalue": ["devalue@5.7.1", "", {}, "sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA=="], + "devalue": ["devalue@5.8.0", "", {}, "sha512-2zA9pFEsnp7vWBZbXF5JAgAq0fsUIt/1XPbRiAmRV3lp/2C3upzH+sADiyy66aFCihoLEsrQHxNM5w1gIDfsBg=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], @@ -2370,7 +2377,7 @@ "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], - "electron-to-chromium": ["electron-to-chromium@1.5.344", "", {}, "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg=="], + "electron-to-chromium": ["electron-to-chromium@1.5.352", "", {}, "sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg=="], "elysia": ["elysia@1.4.28", "", { "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "peerDependencies": { "@sinclair/typebox": ">= 0.34.0 < 1", "@types/bun": ">= 1.2.0", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "optionalPeers": ["@types/bun", "typescript"] }, "sha512-Vrx8sBnvq8squS/3yNBzR1jBXI+SgmnmvwawPjNuEHndUe5l1jV2Gp6JJ4ulDkEB8On6bWmmuyPpA+bq4t+WYg=="], @@ -2384,7 +2391,7 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], - "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], + "enhanced-resolve": ["enhanced-resolve@5.21.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-8p7DUVq6XJnZEz9W4oSwiwycxBIjHjRzYb3Je3zVN+geKTRQKzAkR/K4PBExlS0090d9nshak6phMUxr3PDjmQ=="], "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], @@ -2414,7 +2421,7 @@ "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], - "es-toolkit": ["es-toolkit@1.46.0", "", {}, "sha512-IToJ6ct9OLl5zz6WsC/1vZEwfSZ7Myil+ygl5Tf30Xjn9AEkzNB4kqp2G7VUJKF1DtTx/ra5M5KLlXvzOg51BA=="], + "es-toolkit": ["es-toolkit@1.46.1", "", {}, "sha512-5eNtXOs3tbfxXOj04tjjseeWkRWaoCjdEI+96DgwzZoe6c9juL49pXlzAFTI72aWC9Y8p7168g6XIKjh7k6pyQ=="], "esbuild": ["esbuild@0.27.3", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.3", "@esbuild/android-arm": "0.27.3", "@esbuild/android-arm64": "0.27.3", "@esbuild/android-x64": "0.27.3", "@esbuild/darwin-arm64": "0.27.3", "@esbuild/darwin-x64": "0.27.3", "@esbuild/freebsd-arm64": "0.27.3", "@esbuild/freebsd-x64": "0.27.3", "@esbuild/linux-arm": "0.27.3", "@esbuild/linux-arm64": "0.27.3", "@esbuild/linux-ia32": "0.27.3", "@esbuild/linux-loong64": "0.27.3", "@esbuild/linux-mips64el": "0.27.3", "@esbuild/linux-ppc64": "0.27.3", "@esbuild/linux-riscv64": "0.27.3", "@esbuild/linux-s390x": "0.27.3", "@esbuild/linux-x64": "0.27.3", "@esbuild/netbsd-arm64": "0.27.3", "@esbuild/netbsd-x64": "0.27.3", "@esbuild/openbsd-arm64": "0.27.3", "@esbuild/openbsd-x64": "0.27.3", "@esbuild/openharmony-arm64": "0.27.3", "@esbuild/sunos-x64": "0.27.3", "@esbuild/win32-arm64": "0.27.3", "@esbuild/win32-ia32": "0.27.3", "@esbuild/win32-x64": "0.27.3" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg=="], @@ -2430,7 +2437,7 @@ "eslint-config-prettier": ["eslint-config-prettier@9.1.2", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ=="], - "eslint-config-universe": ["eslint-config-universe@15.0.3", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "^8.29.1", "@typescript-eslint/parser": "^8.29.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-n": "^17.17.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "globals": "^16.0.0" }, "peerDependencies": { "eslint": ">=8.10", "prettier": ">=3" }, "optionalPeers": ["prettier"] }, "sha512-fUMsNXp7GJBu7Sz9PXFBbXhkiixdQ5sbnViFIBbk6ORAfeokczJ+eVv5HQ2gwxPQdbfJarpkO9WZDtxIvJnEGw=="], + "eslint-config-universe": ["eslint-config-universe@15.0.4", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "^8.29.1", "@typescript-eslint/parser": "^8.29.1", "eslint-config-prettier": "^9.1.0", "eslint-plugin-import": "^2.31.0", "eslint-plugin-n": "^17.17.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^5.2.6", "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "globals": "^16.0.0" }, "peerDependencies": { "eslint": ">=8.10", "prettier": ">=3" }, "optionalPeers": ["prettier"] }, "sha512-7XTb/JTLzntJTUHXnR7ADl78kzRpQLm75NOjx1kYFnEMArJk69mDJ96WREzttro4/TOlQ9paGL+WFsRXk1vLkw=="], "eslint-import-resolver-node": ["eslint-import-resolver-node@0.3.10", "", { "dependencies": { "debug": "^3.2.7", "is-core-module": "^2.16.1", "resolve": "^2.0.0-next.6" } }, "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ=="], @@ -2552,7 +2559,7 @@ "expo-secure-store": ["expo-secure-store@15.0.8", "", { "peerDependencies": { "expo": "*" } }, "sha512-lHnzvRajBu4u+P99+0GEMijQMFCOYpWRO4dWsXSuMt77+THPIGjzNvVKrGSl6mMrLsfVaKL8BpwYZLGlgA+zAw=="], - "expo-server": ["expo-server@1.0.5", "", {}, "sha512-IGR++flYH70rhLyeXF0Phle56/k4cee87WeQ4mamS+MkVAVP+dDlOHf2nN06Z9Y2KhU0Gp1k+y61KkghF7HdhA=="], + "expo-server": ["expo-server@1.0.6", "", {}, "sha512-vb5TBtskvEdzYuW79lATXutOEBfW5m6U4EFpNjCVZTnI7S//SAsLQkYEpn+EDfn84m6VQfzSGkIVR6YPaScKFA=="], "expo-sqlite": ["expo-sqlite@16.0.10", "", { "dependencies": { "await-lock": "^2.2.2" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-tUOKxE9TpfneRG3eOfbNfhN9236SJ7IiUnP8gCqU7umd9DtgDGB/5PhYVVfl+U7KskgolgNoB9v9OZ9iwXN8Eg=="], @@ -2576,7 +2583,7 @@ "express": ["express@5.2.1", "", { "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", "content-disposition": "^1.0.0", "content-type": "^1.0.5", "cookie": "^0.7.1", "cookie-signature": "^1.2.1", "debug": "^4.4.0", "depd": "^2.0.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "etag": "^1.8.1", "finalhandler": "^2.1.0", "fresh": "^2.0.0", "http-errors": "^2.0.0", "merge-descriptors": "^2.0.0", "mime-types": "^3.0.0", "on-finished": "^2.4.1", "once": "^1.4.0", "parseurl": "^1.3.3", "proxy-addr": "^2.0.7", "qs": "^6.14.0", "range-parser": "^1.2.1", "router": "^2.2.0", "send": "^1.1.0", "serve-static": "^2.2.0", "statuses": "^2.0.1", "type-is": "^2.0.1", "vary": "^1.1.2" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], - "express-rate-limit": ["express-rate-limit@8.4.1", "", { "dependencies": { "ip-address": "10.1.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-NGVYwQSAyEQgzxX1iCM978PP9AdO/hW93gMcF6ZwQCm+rFvLsBH6w4xcXWTcliS8La5EPRN3p9wzItqBwJrfNw=="], + "express-rate-limit": ["express-rate-limit@8.5.1", "", { "dependencies": { "ip-address": "^10.2.0" }, "peerDependencies": { "express": ">= 4.11" } }, "sha512-5O6KYmyJEpuPJV5hNTXKbAHWRqrzyu+OI3vUnSd2kXFubIVpG7ezpgxQy76Zo5GQZtrQBg86hF+CM/NX+cioiQ=="], "exsolve": ["exsolve@1.0.8", "", {}, "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA=="], @@ -2600,7 +2607,7 @@ "fast-sha256": ["fast-sha256@1.3.0", "", {}, "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ=="], - "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "fast-uri": ["fast-uri@3.1.2", "", {}, "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ=="], "fast-xml-parser": ["fast-xml-parser@4.4.1", "", { "dependencies": { "strnum": "^1.0.5" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw=="], @@ -2654,7 +2661,7 @@ "fs": ["fs@0.0.1-security", "", {}, "sha512-3XY9e1pP0CVEUCdj5BmfIZxRBTSDycnbqhIOGec9QYtmVH2fbLpj86CFWkrNOkt/Fvty4KZG5lTglL9j/gJ87w=="], - "fs-extra": ["fs-extra@11.3.4", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA=="], + "fs-extra": ["fs-extra@11.3.5", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg=="], "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], @@ -2750,7 +2757,7 @@ "hoist-non-react-statics": ["hoist-non-react-statics@3.3.2", "", { "dependencies": { "react-is": "^16.7.0" } }, "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw=="], - "hono": ["hono@4.12.15", "", {}, "sha512-qM0jDhFEaCBb4TxoW7f53Qrpv9RBiayUHo0S52JudprkhvpjIrGoU1mnnr29Fvd1U335ZFPZQY1wlkqgfGXyLg=="], + "hono": ["hono@4.12.18", "", {}, "sha512-RWzP96k/yv0PQfyXnWjs6zot20TqfpfsNXhOnev8d1InAxubW93L11/oNUc3tQqn2G0bSdAOBpX+2uDFHV7kdQ=="], "hosted-git-info": ["hosted-git-info@7.0.2", "", { "dependencies": { "lru-cache": "^10.0.1" } }, "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w=="], @@ -2804,7 +2811,7 @@ "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], - "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + "ip-address": ["ip-address@10.2.0", "", {}, "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA=="], "ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], @@ -2822,7 +2829,7 @@ "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], - "is-core-module": ["is-core-module@2.16.1", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w=="], + "is-core-module": ["is-core-module@2.16.2", "", { "dependencies": { "hasown": "^2.0.3" } }, "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA=="], "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], @@ -2924,7 +2931,7 @@ "jose": ["jose@5.10.0", "", {}, "sha512-s+3Al/p9g32Iq+oqXxkW//7jk2Vig6FF1CFqzVXoTUXt2qz89YWbL+OwS17NFYEvxC35n0FKeGO2LGYSxeM2Gg=="], - "jotai": ["jotai@2.19.1", "", { "peerDependencies": { "@babel/core": ">=7.0.0", "@babel/template": ">=7.0.0", "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@babel/core", "@babel/template", "@types/react", "react"] }, "sha512-sqm9lVZiqBHZH8aSRk32DSiZDHY3yUIlulXYn9GQj7/LvoUdYXSMti7ZPJGo+6zjzKFt5a25k/I6iBCi43PJcw=="], + "jotai": ["jotai@2.20.0", "", { "peerDependencies": { "@babel/core": ">=7.0.0", "@babel/template": ">=7.0.0", "@types/react": ">=17.0.0", "react": ">=17.0.0" }, "optionalPeers": ["@babel/core", "@babel/template", "@types/react", "react"] }, "sha512-b5GAqgmXmXzB4WPaTH26ppk9Sl7AA9WSQX7yfdM+gJ1rFROiWcVbi97gFuN/yVCojOcbcvop2sfLL+fjxW0JVg=="], "js-base64": ["js-base64@3.7.8", "", {}, "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow=="], @@ -2968,6 +2975,8 @@ "lan-network": ["lan-network@0.2.1", "", { "bin": { "lan-network": "dist/lan-network-cli.js" } }, "sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A=="], + "leaflet": ["leaflet@1.9.4", "", {}, "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="], + "lefthook": ["lefthook@1.13.6", "", { "optionalDependencies": { "lefthook-darwin-arm64": "1.13.6", "lefthook-darwin-x64": "1.13.6", "lefthook-freebsd-arm64": "1.13.6", "lefthook-freebsd-x64": "1.13.6", "lefthook-linux-arm64": "1.13.6", "lefthook-linux-x64": "1.13.6", "lefthook-openbsd-arm64": "1.13.6", "lefthook-openbsd-x64": "1.13.6", "lefthook-windows-arm64": "1.13.6", "lefthook-windows-x64": "1.13.6" }, "bin": { "lefthook": "bin/index.js" } }, "sha512-ojj4/4IJ29Xn4drd5emqVgilegAPN3Kf0FQM2p/9+lwSTpU+SZ1v4Ig++NF+9MOa99UKY8bElmVrLhnUUNFh5g=="], "lefthook-darwin-arm64": ["lefthook-darwin-arm64@1.13.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-m6Lb77VGc84/Qo21Lhq576pEvcgFCnvloEiP02HbAHcIXD0RTLy9u2yAInrixqZeaz13HYtdDaI7OBYAAdVt8A=="], @@ -3052,9 +3061,9 @@ "loupe": ["loupe@3.2.1", "", {}, "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ=="], - "lru-cache": ["lru-cache@11.3.5", "", {}, "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw=="], + "lru-cache": ["lru-cache@11.3.6", "", {}, "sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A=="], - "lucide-react": ["lucide-react@1.11.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-UOhjdztXCgdBReRcIhsvz2siIBogfv/lhJEIViCpLt924dO+GDms9T7DNoucI23s6kEPpe988m5N0D2ajnzb2g=="], + "lucide-react": ["lucide-react@1.14.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-+1mdWcfSJVUsaTIjN9zoezmUhfXo5l0vP7ekBMPo3jcS/aIkxHnXqAPsByszMZx/Y8oQBRJxJx5xg+RH3urzxA=="], "magic-regexp": ["magic-regexp@0.11.0", "", { "dependencies": { "magic-string": "^0.30.21", "regexp-tree": "^0.1.27", "type-level-regexp": "~0.1.17", "unplugin": "^3.0.0" } }, "sha512-LG77Z/gVnwz7oaDpD4heX6ryl+lcr4l1B2gnP4MMvt2pGhGC1Dfj7dl1pXpP4ih+VQFLuAadeKVa+lARAzfW+Q=="], @@ -3138,9 +3147,9 @@ "metro-resolver": ["metro-resolver@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ=="], - "metro-runtime": ["metro-runtime@0.83.6", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-WQPua1G2VgYbwRn6vSKxOhTX7CFbSf/JdUu6Nd8bZnPXckOf7HQ2y51NXNQHoEsiuawathrkzL8pBhv+zgZFmg=="], + "metro-runtime": ["metro-runtime@0.83.7", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-9GKkJURaB2iyYoEExKnedzAHzxmKtSi+k0tsZUvMoU27tBZJElchYt7JH/Ai/XzYAI9lCAaV7u5HZSI8J5Z+wQ=="], - "metro-source-map": ["metro-source-map@0.83.6", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.6", "nullthrows": "^1.1.1", "ob1": "0.83.6", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-AqJbOMMpeyyM4iNI91pchqDIszzNuuHApEhg6OABqZ+9mjLEqzcIEQ/fboZ7x74fNU5DBd2K36FdUQYPqlGClA=="], + "metro-source-map": ["metro-source-map@0.83.7", "", { "dependencies": { "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.7", "nullthrows": "^1.1.1", "ob1": "0.83.7", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-JgA1h7oc1a1jydBe1GhVFsUoMYo3wLPk7oRA32rjlDsq+sP2JLt9x2p2lWbNSxTm/u8NV4VRid3hvEJgcX8tKw=="], "metro-symbolicate": ["metro-symbolicate@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.3", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw=="], @@ -3238,7 +3247,7 @@ "mz": ["mz@2.7.0", "", { "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", "thenify-all": "^1.0.0" } }, "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q=="], - "nanoid": ["nanoid@5.1.9", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-ZUvP7KeBLe3OZ1ypw6dI/TzYJuvHP77IM4Ry73waSQTLn8/g8rpdjfyVAh7t1/+FjBtG4lCP42MEbDxOsRpBMw=="], + "nanoid": ["nanoid@5.1.11", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-v+KEsUv2ps74PaSKv0gHTxTCgMXOIfBEbaqa6w6ISIGC7ZsvHN4N9oJ8d4cmf0n5oTzQz2SLmThbQWhjd/8eKg=="], "nativewind": ["nativewind@4.2.3", "", { "dependencies": { "comment-json": "^4.2.5", "debug": "^4.3.7", "react-native-css-interop": "0.2.3" }, "peerDependencies": { "tailwindcss": ">3.3.0" } }, "sha512-HglF1v6A8CqBFpXWs0d3yf4qQGurrreLuyE8FTRI/VDH8b0npZa2SDG5tviTkLiBg0s5j09mQALZOjxuocgMLA=="], @@ -3248,7 +3257,7 @@ "nested-error-stacks": ["nested-error-stacks@2.0.1", "", {}, "sha512-SrQrok4CATudVzBS7coSz26QRSmlK9TzzoFbeKfcPBUFPjcQM9Rqvr/DlJkOrwI/0KcgvMub1n1g5Jt9EgRn4A=="], - "next": ["next@15.5.15", "", { "dependencies": { "@next/env": "15.5.15", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.15", "@next/swc-darwin-x64": "15.5.15", "@next/swc-linux-arm64-gnu": "15.5.15", "@next/swc-linux-arm64-musl": "15.5.15", "@next/swc-linux-x64-gnu": "15.5.15", "@next/swc-linux-x64-musl": "15.5.15", "@next/swc-win32-arm64-msvc": "15.5.15", "@next/swc-win32-x64-msvc": "15.5.15", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-VSqCrJwtLVGwAVE0Sb/yikrQfkwkZW9p+lL/J4+xe+G3ZA+QnWPqgcfH1tDUEuk9y+pthzzVFp4L/U8JerMfMQ=="], + "next": ["next@15.5.18", "", { "dependencies": { "@next/env": "15.5.18", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "15.5.18", "@next/swc-darwin-x64": "15.5.18", "@next/swc-linux-arm64-gnu": "15.5.18", "@next/swc-linux-arm64-musl": "15.5.18", "@next/swc-linux-x64-gnu": "15.5.18", "@next/swc-linux-x64-musl": "15.5.18", "@next/swc-win32-arm64-msvc": "15.5.18", "@next/swc-win32-x64-msvc": "15.5.18", "sharp": "^0.34.3" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-eKL8zUJkX9Y5lE+RX/2YJoItVdGlIscyVyboeD9wSpp0PaGqjoA4tTpT2qPqz9ax+5IzGESyLSeZ/RCwbSZ2uQ=="], "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], @@ -3278,7 +3287,7 @@ "nuqs": ["nuqs@2.8.9", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^5 || ^6 || ^7", "react-router-dom": "^5 || ^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-8ou6AEwsxMWSYo2qkfZtYFVzngwbKmg4c00HVxC1fF6CEJv3Fwm6eoZmfVPALB+vw8Udo7KL5uy96PFcYe1BIQ=="], - "ob1": ["ob1@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-m/xZYkwcjo6UqLMrUICEB3iHk7Bjt3RSR7KXMi6Y1MO/kGkPhoRmfUDF6KAan3rLAZ7ABRqnQyKUTwaqZgUV4w=="], + "ob1": ["ob1@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-9M5kpuOLyTPogMtZiQUIxdAZxl7Dxs6tVBbJErSumsqGMuhVSoUbkfeZ3XNPpLpwBBtqY5QDUzGwggLHX3slQg=="], "object-assign": ["object-assign@4.0.1", "", {}, "sha512-c6legOHWepAbWnp3j5SRUMpxCXBKI4rD7A5Osn9IzZ8w4O/KccXdW0lqdkQKbpk0eHGjNgKihgzY6WuEq99Tfw=="], @@ -3402,7 +3411,7 @@ "pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], - "plist": ["plist@3.1.0", "", { "dependencies": { "@xmldom/xmldom": "^0.8.8", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ=="], + "plist": ["plist@3.1.1", "", { "dependencies": { "@xmldom/xmldom": "^0.9.10", "base64-js": "^1.5.1", "xmlbuilder": "^15.1.1" } }, "sha512-ZIfcLJC+7E7FBFnDxm9MPmt7D+DidyQ26lewieO75AdhA2ayMtsJSES0iWzqJQbcVRSrTufQoy0DR94xHue0oA=="], "pngjs": ["pngjs@3.4.0", "", {}, "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w=="], @@ -3410,7 +3419,7 @@ "postal-mime": ["postal-mime@2.7.4", "", {}, "sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g=="], - "postcss": ["postcss@8.5.10", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ=="], + "postcss": ["postcss@8.5.14", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg=="], "postcss-import": ["postcss-import@16.1.1", "", { "dependencies": { "postcss-value-parser": "^4.0.0", "read-cache": "^1.0.0", "resolve": "^1.1.7" }, "peerDependencies": { "postcss": "^8.0.0" } }, "sha512-2xVS1NCZAfjtVdvXiyegxzJ447GyqCeEI5V7ApgQVOWnros1p5lGNovJNapwPpMombyFBfqDwt7AD3n2l0KOfQ=="], @@ -3438,7 +3447,7 @@ "prettier-linter-helpers": ["prettier-linter-helpers@1.0.1", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-SxToR7P8Y2lWmv/kTzVLC1t/GDI2WGjMwNhLLE9qtH8Q13C+aEmuRlzDst4Up4s0Wc8sF2M+J57iB3cMLqftfg=="], - "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.3", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-lckXaWWdo2ZVXoMoUO3WIBiz9hVY+YBEh1gYyMFfrWP9WZW/wpFXQKizHx7WrFQFMkcG0bGShdpp531X1n+qpg=="], + "prettier-plugin-tailwindcss": ["prettier-plugin-tailwindcss@0.7.4", "", { "peerDependencies": { "@ianvs/prettier-plugin-sort-imports": "*", "@prettier/plugin-hermes": "*", "@prettier/plugin-oxc": "*", "@prettier/plugin-pug": "*", "@shopify/prettier-plugin-liquid": "*", "@trivago/prettier-plugin-sort-imports": "*", "@zackad/prettier-plugin-twig": "*", "prettier": "^3.0", "prettier-plugin-astro": "*", "prettier-plugin-css-order": "*", "prettier-plugin-jsdoc": "*", "prettier-plugin-marko": "*", "prettier-plugin-multiline-arrays": "*", "prettier-plugin-organize-attributes": "*", "prettier-plugin-organize-imports": "*", "prettier-plugin-sort-imports": "*", "prettier-plugin-svelte": "*" }, "optionalPeers": ["@ianvs/prettier-plugin-sort-imports", "@prettier/plugin-hermes", "@prettier/plugin-oxc", "@prettier/plugin-pug", "@shopify/prettier-plugin-liquid", "@trivago/prettier-plugin-sort-imports", "@zackad/prettier-plugin-twig", "prettier-plugin-astro", "prettier-plugin-css-order", "prettier-plugin-jsdoc", "prettier-plugin-marko", "prettier-plugin-multiline-arrays", "prettier-plugin-organize-attributes", "prettier-plugin-organize-imports", "prettier-plugin-sort-imports", "prettier-plugin-svelte"] }, "sha512-UKii4RjY05SNt/WQi6/NcOn/LsT0/ILLXsxygjbRg5/YZelsSu5jTqorYHPDGq4nZy5q5hpCu+XdGZ1xaJEQgw=="], "pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="], @@ -3498,11 +3507,13 @@ "react-freeze": ["react-freeze@1.0.4", "", { "peerDependencies": { "react": ">=17.0.0" } }, "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA=="], - "react-hook-form": ["react-hook-form@7.73.1", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-VAfVYOPcx3piiEVQy95vyFmBwbVUsP/AUIN+mpFG8h11yshDd444nn0VyfaGWSRnhOLVgiDu7HIuBtAIzxn9dA=="], + "react-hook-form": ["react-hook-form@7.75.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-Ovv94H+0p3sJ7B9B5QxPuCP1u8V/cHuVGyH55cSwodYDtoJwK+fqk3vjfIgSX59I2U/bU4z0nRJ9HMLpNiWEmw=="], + + "react-i18next": ["react-i18next@17.0.7", "", { "dependencies": { "@babel/runtime": "^7.29.2", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.0.10", "react": ">= 16.8.0", "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-rwtPXsb/zwzDafN+gytcjF5YnqGQQIRmCQ6DctBC1VSipRB8GD/MWEVrFP42vjMyuYydxWxM8CZRt+yiNuuoHg=="], - "react-i18next": ["react-i18next@17.0.4", "", { "dependencies": { "@babel/runtime": "^7.29.2", "html-parse-stringify": "^3.0.1", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "i18next": ">= 26.0.1", "react": ">= 16.8.0", "typescript": "^5 || ^6" }, "optionalPeers": ["typescript"] }, "sha512-hQipmK4EF0y6RO6tt6WuqnmWpWYEXmQUUzecmMBuNsIgYd3smXcG4GtYPWhvgxn0pqMOItKlEO8H24HCs5hc3g=="], + "react-is": ["react-is@19.2.6", "", {}, "sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw=="], - "react-is": ["react-is@19.2.5", "", {}, "sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ=="], + "react-leaflet": ["react-leaflet@5.0.0", "", { "dependencies": { "@react-leaflet/core": "^3.0.0" }, "peerDependencies": { "leaflet": "^1.9.0", "react": "^19.0.0", "react-dom": "^19.0.0" } }, "sha512-CWbTpr5vcHw5bt9i4zSlPEVQdTVcML390TjeDG0cK59z1ylexpqC6M1PJFjV8jD7CF+ACBFsLIDs6DRMoLEofw=="], "react-native": ["react-native@0.81.5", "", { "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", "@react-native/codegen": "0.81.5", "@react-native/community-cli-plugin": "0.81.5", "@react-native/gradle-plugin": "0.81.5", "@react-native/js-polyfills": "0.81.5", "@react-native/normalize-colors": "0.81.5", "@react-native/virtualized-lists": "0.81.5", "abort-controller": "^3.0.0", "anser": "^1.4.9", "ansi-regex": "^5.0.0", "babel-jest": "^29.7.0", "babel-plugin-syntax-hermes-parser": "0.29.1", "base64-js": "^1.5.1", "commander": "^12.0.0", "flow-enums-runtime": "^0.0.6", "glob": "^7.1.1", "invariant": "^2.2.4", "jest-environment-node": "^29.7.0", "memoize-one": "^5.0.0", "metro-runtime": "^0.83.1", "metro-source-map": "^0.83.1", "nullthrows": "^1.1.1", "pretty-format": "^29.7.0", "promise": "^8.3.0", "react-devtools-core": "^6.1.5", "react-refresh": "^0.14.0", "regenerator-runtime": "^0.13.2", "scheduler": "0.26.0", "semver": "^7.1.3", "stacktrace-parser": "^0.1.10", "whatwg-fetch": "^3.0.0", "ws": "^6.2.3", "yargs": "^17.6.2" }, "peerDependencies": { "@types/react": "^19.1.0", "react": "^19.1.0" }, "optionalPeers": ["@types/react"], "bin": { "react-native": "cli.js" } }, "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw=="], @@ -3550,7 +3561,7 @@ "react-remove-scroll-bar": ["react-remove-scroll-bar@2.3.8", "", { "dependencies": { "react-style-singleton": "^2.2.2", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" }, "optionalPeers": ["@types/react"] }, "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q=="], - "react-resizable-panels": ["react-resizable-panels@4.10.0", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-frjewRQt7TCv/vCH1pJfjZ7RxAhr5pKuqVQtVgzFq/vherxBFOWyC3xMbryx5Ti2wylViGUFc93Etg4rB3E0UA=="], + "react-resizable-panels": ["react-resizable-panels@4.11.0", "", { "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-LPk/AkFDGkg7SsbOyL93ojrE6E7lhrxxDwnYNjfmnSeI6BE7Sje6dB24PXgZk8DeugdeXNk1LO+ohRqIjhxiLw=="], "react-style-singleton": ["react-style-singleton@2.2.3", "", { "dependencies": { "get-nonce": "^1.0.0", "tslib": "^2.0.0" }, "peerDependencies": { "@types/react": "*", "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ=="], @@ -3616,7 +3627,7 @@ "reselect": ["reselect@5.1.1", "", {}, "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="], - "resend": ["resend@6.12.2", "", { "dependencies": { "postal-mime": "2.7.4", "svix": "1.90.0" }, "peerDependencies": { "@react-email/render": "*" }, "optionalPeers": ["@react-email/render"] }, "sha512-xwgmU4b0OqoabJsIoK/x0Whk0Fcs3bpbK4i/DEWPiE5hYJHyHl0TbB6QbI3gIr+bLdLUJ1GYm/fe41aVFuHXgw=="], + "resend": ["resend@6.12.3", "", { "dependencies": { "postal-mime": "2.7.4", "svix": "1.92.2" }, "peerDependencies": { "@react-email/render": "*" }, "optionalPeers": ["@react-email/render"] }, "sha512-FkEi6YPnVL96/LvH8+QP7NaeaBy5brYXwlRqUCqZZeNL0/iyKij18IPmyPXYauT/2ODn1JG04qKz+qlJfzqzTw=="], "resolve": ["resolve@1.22.12", "", { "dependencies": { "es-errors": "^1.3.0", "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": { "resolve": "bin/resolve" } }, "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA=="], @@ -3636,9 +3647,9 @@ "rn-icon-mapper": ["rn-icon-mapper@0.0.1", "", {}, "sha512-RBGgyo4WUnFQg6lnHfz3R5Gyeh/z5n05kdhcsgD9a19RHU+sTplQYLHhWUcXvLyCjay1YJgTNJX0o8toWV3Tuw=="], - "rolldown": ["rolldown@1.0.0-rc.17", "", { "dependencies": { "@oxc-project/types": "=0.127.0", "@rolldown/pluginutils": "1.0.0-rc.17" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-arm64": "1.0.0-rc.17", "@rolldown/binding-darwin-x64": "1.0.0-rc.17", "@rolldown/binding-freebsd-x64": "1.0.0-rc.17", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.17", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.17", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.17", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA=="], + "rolldown": ["rolldown@1.0.0", "", { "dependencies": { "@oxc-project/types": "=0.129.0", "@rolldown/pluginutils": "1.0.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0", "@rolldown/binding-darwin-arm64": "1.0.0", "@rolldown/binding-darwin-x64": "1.0.0", "@rolldown/binding-freebsd-x64": "1.0.0", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", "@rolldown/binding-linux-arm64-gnu": "1.0.0", "@rolldown/binding-linux-arm64-musl": "1.0.0", "@rolldown/binding-linux-ppc64-gnu": "1.0.0", "@rolldown/binding-linux-s390x-gnu": "1.0.0", "@rolldown/binding-linux-x64-gnu": "1.0.0", "@rolldown/binding-linux-x64-musl": "1.0.0", "@rolldown/binding-openharmony-arm64": "1.0.0", "@rolldown/binding-wasm32-wasi": "1.0.0", "@rolldown/binding-win32-arm64-msvc": "1.0.0", "@rolldown/binding-win32-x64-msvc": "1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA=="], - "rollup": ["rollup@4.60.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.2", "@rollup/rollup-android-arm64": "4.60.2", "@rollup/rollup-darwin-arm64": "4.60.2", "@rollup/rollup-darwin-x64": "4.60.2", "@rollup/rollup-freebsd-arm64": "4.60.2", "@rollup/rollup-freebsd-x64": "4.60.2", "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", "@rollup/rollup-linux-arm-musleabihf": "4.60.2", "@rollup/rollup-linux-arm64-gnu": "4.60.2", "@rollup/rollup-linux-arm64-musl": "4.60.2", "@rollup/rollup-linux-loong64-gnu": "4.60.2", "@rollup/rollup-linux-loong64-musl": "4.60.2", "@rollup/rollup-linux-ppc64-gnu": "4.60.2", "@rollup/rollup-linux-ppc64-musl": "4.60.2", "@rollup/rollup-linux-riscv64-gnu": "4.60.2", "@rollup/rollup-linux-riscv64-musl": "4.60.2", "@rollup/rollup-linux-s390x-gnu": "4.60.2", "@rollup/rollup-linux-x64-gnu": "4.60.2", "@rollup/rollup-linux-x64-musl": "4.60.2", "@rollup/rollup-openbsd-x64": "4.60.2", "@rollup/rollup-openharmony-arm64": "4.60.2", "@rollup/rollup-win32-arm64-msvc": "4.60.2", "@rollup/rollup-win32-ia32-msvc": "4.60.2", "@rollup/rollup-win32-x64-gnu": "4.60.2", "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ=="], + "rollup": ["rollup@4.60.3", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.3", "@rollup/rollup-android-arm64": "4.60.3", "@rollup/rollup-darwin-arm64": "4.60.3", "@rollup/rollup-darwin-x64": "4.60.3", "@rollup/rollup-freebsd-arm64": "4.60.3", "@rollup/rollup-freebsd-x64": "4.60.3", "@rollup/rollup-linux-arm-gnueabihf": "4.60.3", "@rollup/rollup-linux-arm-musleabihf": "4.60.3", "@rollup/rollup-linux-arm64-gnu": "4.60.3", "@rollup/rollup-linux-arm64-musl": "4.60.3", "@rollup/rollup-linux-loong64-gnu": "4.60.3", "@rollup/rollup-linux-loong64-musl": "4.60.3", "@rollup/rollup-linux-ppc64-gnu": "4.60.3", "@rollup/rollup-linux-ppc64-musl": "4.60.3", "@rollup/rollup-linux-riscv64-gnu": "4.60.3", "@rollup/rollup-linux-riscv64-musl": "4.60.3", "@rollup/rollup-linux-s390x-gnu": "4.60.3", "@rollup/rollup-linux-x64-gnu": "4.60.3", "@rollup/rollup-linux-x64-musl": "4.60.3", "@rollup/rollup-openbsd-x64": "4.60.3", "@rollup/rollup-openharmony-arm64": "4.60.3", "@rollup/rollup-win32-arm64-msvc": "4.60.3", "@rollup/rollup-win32-ia32-msvc": "4.60.3", "@rollup/rollup-win32-x64-gnu": "4.60.3", "@rollup/rollup-win32-x64-msvc": "4.60.3", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-pAQK9HalE84QSm4Po3EmWIZPd3FnjkShVkiMlz1iligWYkWQ7wHYd1PF/T7QZ5TVSD6uSTon5gBVMSM4JfBV+A=="], "router": ["router@2.2.0", "", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], @@ -3820,7 +3831,7 @@ "supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="], - "svix": ["svix@1.90.0", "", { "dependencies": { "standardwebhooks": "1.0.0", "uuid": "^10.0.0" } }, "sha512-ljkZuyy2+IBEoESkIpn8sLM+sxJHQcPxlZFxU+nVDhltNfUMisMBzWX/UR8SjEnzoI28ZjCzMbmYAPwSTucoMw=="], + "svix": ["svix@1.92.2", "", { "dependencies": { "standardwebhooks": "1.0.0" } }, "sha512-ZmuA3UVvlnF9EgxlzmPtF7CKjQb64Z6OFlyfdDfU0sdcC7dJa+3aOYX5B9mA+RS6ch1AxBa4UP/l6KmqfGtWBQ=="], "swr": ["swr@2.4.1", "", { "dependencies": { "dequal": "^2.0.3", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-2CC6CiKQtEwaEeNiqWTAw9PGykW8SR5zZX8MZk6TeAvEAnVS7Visz8WzphqgtQ8v2xz/4Q5K+j+SeMaKXeeQIA=="], @@ -3836,11 +3847,11 @@ "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], - "tar": ["tar@7.5.13", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng=="], + "tar": ["tar@7.5.15", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="], "terminal-link": ["terminal-link@2.1.1", "", { "dependencies": { "ansi-escapes": "^4.2.1", "supports-hyperlinks": "^2.0.0" } }, "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ=="], - "terser": ["terser@5.46.2", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-uxfo9fPcSgLDYob/w1FuL0c99MWiJDnv+5qXSQc5+Ki5NjVNsYi66INnMFBjf6uFz6OnX12piJQPF4IpjJTNTw=="], + "terser": ["terser@5.47.1", "", { "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, "bin": { "terser": "bin/terser" } }, "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw=="], "test-exclude": ["test-exclude@7.0.2", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^10.4.1", "minimatch": "^10.2.2" } }, "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw=="], @@ -3858,7 +3869,7 @@ "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], - "tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], + "tinyexec": ["tinyexec@1.1.2", "", {}, "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA=="], "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], @@ -3928,7 +3939,7 @@ "uc.micro": ["uc.micro@1.0.6", "", {}, "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA=="], - "ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="], + "ufo": ["ufo@1.6.4", "", {}, "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA=="], "uhyphen": ["uhyphen@0.2.0", "", {}, "sha512-qz3o9CHXmJJPGBdqzab7qAYuW8kQGKNEuoHFYrBwV6hWIMcpAmxDLXojcHfFr9US1Pe6zUswEIJIbLI610fuqA=="], @@ -3988,7 +3999,7 @@ "utils-merge": ["utils-merge@1.0.1", "", {}, "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA=="], - "uuid": ["uuid@10.0.0", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ=="], + "uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], @@ -4048,11 +4059,11 @@ "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], - "workerd": ["workerd@1.20260424.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260424.1", "@cloudflare/workerd-darwin-arm64": "1.20260424.1", "@cloudflare/workerd-linux-64": "1.20260424.1", "@cloudflare/workerd-linux-arm64": "1.20260424.1", "@cloudflare/workerd-windows-64": "1.20260424.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-oKsB0Xo/mfkYMdSACoS06XZg09VUK4rXwHfF/1t3P++sMbwzf4UHQvMO57+zxpEB2nVrY/ZkW0bYFGq4GdAFSQ=="], + "workerd": ["workerd@1.20260507.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260507.1", "@cloudflare/workerd-darwin-arm64": "1.20260507.1", "@cloudflare/workerd-linux-64": "1.20260507.1", "@cloudflare/workerd-linux-arm64": "1.20260507.1", "@cloudflare/workerd-windows-64": "1.20260507.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-z7JhsFSe6+X1b5fUHaVpo15VM1IRMJiLofEkq8iKdCo+Veqc+FUg5lIsuz8NwePxuSKrXtO4ZQpGkQLbPVXFhg=="], "workers-ai-provider": ["workers-ai-provider@0.7.5", "", { "dependencies": { "@ai-sdk/provider": "^1.1.3", "@ai-sdk/provider-utils": "^2.2.8" } }, "sha512-dhCwgc3D65oDDTpH3k8Gf0Ek7KItzvaQidn2N5L5cqLo3WG8GM/4+Nr4rU56o8O3oZRsloB1gUCHYaRv2j7Y0A=="], - "wrangler": ["wrangler@4.85.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.4.2", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260424.0", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260424.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260424.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-93cwt2RPb1qdcmEgPzH7ybiLN4BIKoWpscIX6SywjHrQOeIZrQk2haoc3XMLKtQTmzapxza9OuDD+kMHpsuuhg=="], + "wrangler": ["wrangler@4.90.0", "", { "dependencies": { "@cloudflare/kv-asset-handler": "0.5.0", "@cloudflare/unenv-preset": "2.16.1", "blake3-wasm": "2.1.5", "esbuild": "0.27.3", "miniflare": "4.20260507.1", "path-to-regexp": "6.3.0", "unenv": "2.0.0-rc.24", "workerd": "1.20260507.1" }, "optionalDependencies": { "fsevents": "~2.3.2" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260507.1" }, "optionalPeers": ["@cloudflare/workers-types"], "bin": { "wrangler": "bin/wrangler.js", "wrangler2": "bin/wrangler.js" } }, "sha512-bmNIykl59TfCUn5xQgU7IWylSsPx3LQaPLMSAq2VQHt89CBrcj9qXQ0eYfjBCWA5XTBVgten391evt7xxtXwcA=="], "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], @@ -4076,7 +4087,7 @@ "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], - "yaml": ["yaml@2.8.3", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], + "yaml": ["yaml@2.8.4", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog=="], "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], @@ -4088,7 +4099,7 @@ "youch-core": ["youch-core@0.3.3", "", { "dependencies": { "@poppinss/exception": "^1.2.2", "error-stack-parser-es": "^1.0.5" } }, "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA=="], - "youtube-transcript": ["youtube-transcript@1.3.0", "", {}, "sha512-laWv9RcKIWh6rZUH3hVnOngEvtKAhFMV5UepUO6AgevPYqe2zv8KW/uCkZJDSnPwf5/AdVu0Q66/1RDblKsp6Q=="], + "youtube-transcript": ["youtube-transcript@1.3.1", "", {}, "sha512-NDCjwad113TGybbYF51y9Z4tcwzBHUZWQdF9veULNca18L+FdDbHHtTHIr69WVa3bB90l67S8kN0HtL2JO9fhg=="], "zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], @@ -4096,28 +4107,16 @@ "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], - "zustand": ["zustand@5.0.12", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g=="], + "zustand": ["zustand@5.0.13", "", { "peerDependencies": { "@types/react": ">=18.0.0", "immer": ">=9.0.6", "react": ">=18.0.0", "use-sync-external-store": ">=1.2.0" }, "optionalPeers": ["@types/react", "immer", "react", "use-sync-external-store"] }, "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], "@ai-sdk/provider-utils/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@aws-crypto/crc32/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], - - "@aws-crypto/crc32c/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], - - "@aws-crypto/sha1-browser/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], - "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - "@aws-crypto/sha256-browser/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], - "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - "@aws-crypto/sha256-js/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], - - "@aws-crypto/util/@aws-sdk/types": ["@aws-sdk/types@3.973.8", "", { "dependencies": { "@smithy/types": "^4.14.1", "tslib": "^2.6.2" } }, "sha512-gjlAdtHMbtR9X5iIhVUvbVcy55KnznpC6bkDUWW9z915bi0ckdUr5cjf16Kp6xq0bP5HBD2xzgbL9F9Quv5vUw=="], - "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -4150,8 +4149,6 @@ "@expo/cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "@expo/cli/expo-server": ["expo-server@1.0.6", "", {}, "sha512-vb5TBtskvEdzYuW79lATXutOEBfW5m6U4EFpNjCVZTnI7S//SAsLQkYEpn+EDfn84m6VQfzSGkIVR6YPaScKFA=="], - "@expo/cli/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], "@expo/cli/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -4200,7 +4197,7 @@ "@expo/xcpretty/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - "@gorhom/portal/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "@gorhom/portal/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "@humanwhocodes/config-array/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], @@ -4214,7 +4211,7 @@ "@manypkg/tools/js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], - "@modelcontextprotocol/sdk/jose": ["jose@6.2.2", "", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], + "@modelcontextprotocol/sdk/jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="], "@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="], @@ -4252,33 +4249,37 @@ "@radix-ui/react-tooltip/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@react-native-ai/apple/@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="], + "@react-native-ai/apple/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], - "@react-native-ai/apple/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-60GYsRj5wIJQRcq5YwYJq4KhwLeStceXEJiZdecP1miiH+6FMmrnc7lZDOJoQ6m9lrudEb+uI4LEwddLz5+rPQ=="], + "@react-native-ai/apple/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.25", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CvsRu+32Y8a167s+lrIBtsybvgTHp8j9y+6BeTvLeoW3Q+okw/b4CnNUFOLIXsRaKHQKAH+IHNJPYWywfpw0LA=="], - "@react-native-ai/apple/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "@react-native-ai/apple/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], - "@react-native-ai/llama/@ai-sdk/provider": ["@ai-sdk/provider@2.0.1", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng=="], + "@react-native-ai/llama/@ai-sdk/provider": ["@ai-sdk/provider@2.0.3", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-h88OPkavHTiN9tMn2l5awAznGB0lXzjcLhgR1/rvjB2zlLprsNxbM2tt6OJsHUxduLC3klq0/eqaSf6fX5XVww=="], - "@react-native-ai/llama/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.23", "", { "dependencies": { "@ai-sdk/provider": "2.0.1", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-60GYsRj5wIJQRcq5YwYJq4KhwLeStceXEJiZdecP1miiH+6FMmrnc7lZDOJoQ6m9lrudEb+uI4LEwddLz5+rPQ=="], + "@react-native-ai/llama/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@3.0.25", "", { "dependencies": { "@ai-sdk/provider": "2.0.3", "@standard-schema/spec": "^1.0.0", "eventsource-parser": "^3.0.6" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-CvsRu+32Y8a167s+lrIBtsybvgTHp8j9y+6BeTvLeoW3Q+okw/b4CnNUFOLIXsRaKHQKAH+IHNJPYWywfpw0LA=="], - "@react-native-ai/llama/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "@react-native-ai/llama/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "@react-native/codegen/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "@react-native/community-cli-plugin/metro": ["metro@0.83.7", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "accepts": "^2.0.0", "ci-info": "^2.0.0", "connect": "^3.6.5", "debug": "^4.4.0", "error-stack-parser": "^2.0.6", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "hermes-parser": "0.35.0", "image-size": "^1.0.2", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "jsc-safe-url": "^0.2.2", "lodash.throttle": "^4.1.1", "metro-babel-transformer": "0.83.7", "metro-cache": "0.83.7", "metro-cache-key": "0.83.7", "metro-config": "0.83.7", "metro-core": "0.83.7", "metro-file-map": "0.83.7", "metro-resolver": "0.83.7", "metro-runtime": "0.83.7", "metro-source-map": "0.83.7", "metro-symbolicate": "0.83.7", "metro-transform-plugins": "0.83.7", "metro-transform-worker": "0.83.7", "mime-types": "^3.0.1", "nullthrows": "^1.1.1", "serialize-error": "^2.1.0", "source-map": "^0.5.6", "throat": "^5.0.0", "ws": "^7.5.10", "yargs": "^17.6.2" }, "bin": { "metro": "src/cli.js" } }, "sha512-SPaPEyvTsTmd0LpT7RaZciQyDw2i/JB7+iY9L5VfBo72+psescFxBqpI1TL9dnL+pmnfkU+l/J1mEEGLeF65EQ=="], + + "@react-native/community-cli-plugin/metro-config": ["metro-config@0.83.7", "", { "dependencies": { "connect": "^3.6.5", "flow-enums-runtime": "^0.0.6", "jest-validate": "^29.7.0", "metro": "0.83.7", "metro-cache": "0.83.7", "metro-core": "0.83.7", "metro-runtime": "0.83.7", "yaml": "^2.6.1" } }, "sha512-83mjWFbFOt2GeJ6pFIum5mSnc1uTsZJAtD8o4ej0s4NVsYsA7fB+pHvTfHhFrpeMONaobu2riKavkPei05Er/Q=="], + + "@react-native/community-cli-plugin/metro-core": ["metro-core@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "lodash.throttle": "^4.1.1", "metro-resolver": "0.83.7" } }, "sha512-6yn3w1wnltT6RQl7p7YES2l95ArC+mWrOssEiH8p5/DDrJS65/szf9LsC9JrBv8c5DdvSY3V3f0GRYg0Ox7hCg=="], + "@react-native/dev-middleware/serve-static": ["serve-static@1.16.3", "", { "dependencies": { "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "~0.19.1" } }, "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA=="], "@react-native/dev-middleware/ws": ["ws@6.2.3", "", { "dependencies": { "async-limiter": "~1.0.0" } }, "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA=="], - "@react-navigation/core/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "@react-navigation/core/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], - "@react-navigation/native/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "@react-navigation/native/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], - "@react-navigation/routers/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "@react-navigation/routers/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], - "@reduxjs/toolkit/@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - - "@reduxjs/toolkit/immer": ["immer@11.1.4", "", {}, "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw=="], + "@reduxjs/toolkit/immer": ["immer@11.1.7", "", {}, "sha512-LFVFtAROHcDy1er5UI6nodRFnZ2SgdCXhfNSI+DpObO8N7Pur/muBGsjzH5wpnFHCYhYVQxZskCkV4koQ//3/Q=="], "@sentry/cli/https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], @@ -4286,7 +4287,7 @@ "@tailwindcss/typography/postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="], - "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], + "@typescript-eslint/project-service/@typescript-eslint/types": ["@typescript-eslint/types@8.59.2", "", {}, "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q=="], "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], @@ -4294,9 +4295,11 @@ "@vitest/utils/@vitest/pretty-format": ["@vitest/pretty-format@3.1.4", "", { "dependencies": { "tinyrainbow": "^2.0.0" } }, "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg=="], + "agents/partyserver": ["partyserver@0.5.5", "", { "dependencies": { "nanoid": "^5.1.9" }, "peerDependencies": { "@cloudflare/workers-types": "^4.20260424.1" } }, "sha512-7zub8oV8Od9dY2aXGrgzhX5GLceaWOg7xB5VWXtDcqt2BWVDIOCAgaF0AmBMSu3AXhJHsFdzPnA8SSZdybXMbQ=="], + "agents/yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], - "agents/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], + "agents/zod": ["zod@4.4.3", "", {}, "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ=="], "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="], @@ -4346,9 +4349,9 @@ "eslint/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/type-utils": "8.59.0", "@typescript-eslint/utils": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HyAZtpdkgZwpq8Sz3FSUvCR4c+ScbuWa9AksK2Jweub7w4M3yTz4O11AqVJzLYjy/B9ZWPyc81I+mOdJU/bDQw=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.59.2", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/type-utils": "8.59.2", "@typescript-eslint/utils": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.59.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ=="], - "eslint-config-universe/@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-TI1XGwKbDpo9tRW8UDIXCOeLk55qe9ZFGs8MTKU6/M08HWTw52DD/IYhfQtOEhEdPhLMT26Ka/x7p70nd3dzDg=="], + "eslint-config-universe/@typescript-eslint/parser": ["@typescript-eslint/parser@8.59.2", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/types": "8.59.2", "@typescript-eslint/typescript-estree": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ=="], "eslint-config-universe/globals": ["globals@16.5.0", "", {}, "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ=="], @@ -4388,7 +4391,7 @@ "expo-router/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.0", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ujc+V6r0HNDviYqIK3rW4ffgYiZ8g5DEHrGJVk4x7kTlLXRDILnKX9vAUYeIsLOoDpDJ0ujpqMkjH4w2ofuo6w=="], - "expo-router/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "expo-router/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "expo-router/semver": ["semver@7.6.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="], @@ -4450,8 +4453,6 @@ "mdast-util-find-and-replace/escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], - "meow/object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], - "merge-options/is-plain-obj": ["is-plain-obj@2.1.0", "", {}, "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA=="], "metro/accepts": ["accepts@1.3.8", "", { "dependencies": { "mime-types": "~2.1.34", "negotiator": "0.6.3" } }, "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw=="], @@ -4474,7 +4475,7 @@ "metro-config/metro-runtime": ["metro-runtime@0.83.3", "", { "dependencies": { "@babel/runtime": "^7.25.0", "flow-enums-runtime": "^0.0.6" } }, "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw=="], - "metro-source-map/metro-symbolicate": ["metro-symbolicate@0.83.6", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.6", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-4nvkmv9T7ozhprlPwk/+xm0SVPsxly5kYyMHdNaOlFemFz4df9BanvD46Ac6OISu/4Idinzfk2KVb++6OfzPAQ=="], + "metro-source-map/metro-symbolicate": ["metro-symbolicate@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.7", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw=="], "metro-symbolicate/metro-source-map": ["metro-source-map@0.83.3", "", { "dependencies": { "@babel/traverse": "^7.25.3", "@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3", "@babel/types": "^7.25.2", "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-symbolicate": "0.83.3", "nullthrows": "^1.1.1", "ob1": "0.83.3", "source-map": "^0.5.6", "vlq": "^1.0.0" } }, "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg=="], @@ -4510,7 +4511,9 @@ "p-locate/p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], - "postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "plist/@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="], + + "postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="], @@ -4548,6 +4551,8 @@ "rimraf/glob": ["glob@13.0.6", "", { "dependencies": { "minimatch": "^10.2.2", "minipass": "^7.1.3", "path-scurry": "^2.0.2" } }, "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw=="], + "rollup/@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + "router/path-to-regexp": ["path-to-regexp@8.4.2", "", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], "simple-plist/bplist-parser": ["bplist-parser@0.3.1", "", { "dependencies": { "big-integer": "1.6.x" } }, "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA=="], @@ -4586,12 +4591,10 @@ "workers-ai-provider/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@2.2.8", "", { "dependencies": { "@ai-sdk/provider": "1.1.3", "nanoid": "^3.3.8", "secure-json-parse": "^2.7.0" }, "peerDependencies": { "zod": "^3.23.8" } }, "sha512-fqhG+4sCVv8x7nFzYnFo19ryhAa3w096Kmc3hWxMQfW/TubPOmt3A6tYZhl4mUfQWWQMsuSkLrtjlWuXBVSGQA=="], - "wrangler/miniflare": ["miniflare@4.20260424.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.8", "workerd": "1.20260424.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-B6MKBBd5TJ19daUc3Ae9rWctn1nDA/VCXykXfCsp9fTxyfGxnZY27tJs1caxgE9MWEMMKGbGHouqVtgKbKGxmw=="], + "wrangler/miniflare": ["miniflare@4.20260507.1", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.8", "workerd": "1.20260507.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-PSXBiLExTdZ4UGO/raKCHQauUpYL7F880ZRB7j0+78Rv8h7TsdN2E/iEDK9sK2Y+SPQ5wJSeAa+rDeVKoZZoEw=="], "write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], - "xcode/uuid": ["uuid@7.0.3", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg=="], - "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], @@ -4716,7 +4719,7 @@ "@expo/metro-config/lightningcss/lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], - "@expo/metro-config/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "@expo/metro-config/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "@expo/metro/metro-source-map/ob1": ["ob1@0.83.3", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA=="], @@ -4742,6 +4745,32 @@ "@react-native/codegen/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + "@react-native/community-cli-plugin/metro/ci-info": ["ci-info@2.0.0", "", {}, "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ=="], + + "@react-native/community-cli-plugin/metro/hermes-parser": ["hermes-parser@0.35.0", "", { "dependencies": { "hermes-estree": "0.35.0" } }, "sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA=="], + + "@react-native/community-cli-plugin/metro/metro-babel-transformer": ["metro-babel-transformer@0.83.7", "", { "dependencies": { "@babel/core": "^7.25.2", "flow-enums-runtime": "^0.0.6", "hermes-parser": "0.35.0", "metro-cache-key": "0.83.7", "nullthrows": "^1.1.1" } }, "sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA=="], + + "@react-native/community-cli-plugin/metro/metro-cache": ["metro-cache@0.83.7", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.7" } }, "sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg=="], + + "@react-native/community-cli-plugin/metro/metro-cache-key": ["metro-cache-key@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-W1c2Nmx8MiJTJt+eWhMO08z9VKi3kZOaz99IYGdqeqDgY9j+yZjXl62rUav4Di0heZfh4/n2s722PqRL1OODeg=="], + + "@react-native/community-cli-plugin/metro/metro-file-map": ["metro-file-map@0.83.7", "", { "dependencies": { "debug": "^4.4.0", "fb-watchman": "^2.0.0", "flow-enums-runtime": "^0.0.6", "graceful-fs": "^4.2.4", "invariant": "^2.2.4", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "nullthrows": "^1.1.1", "walker": "^1.0.7" } }, "sha512-+j0F1m+FQYVAQ6syf+mwhIPV5GoFQrkInX8bppuc50IzNsZbMrp8R5H/Sx/K2daQ3YEa9F/XwkeZT8gzJfgeCw=="], + + "@react-native/community-cli-plugin/metro/metro-resolver": ["metro-resolver@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A=="], + + "@react-native/community-cli-plugin/metro/metro-symbolicate": ["metro-symbolicate@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "invariant": "^2.2.4", "metro-source-map": "0.83.7", "nullthrows": "^1.1.1", "source-map": "^0.5.6", "vlq": "^1.0.0" }, "bin": { "metro-symbolicate": "src/index.js" } }, "sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw=="], + + "@react-native/community-cli-plugin/metro/metro-transform-plugins": ["metro-transform-plugins@0.83.7", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "flow-enums-runtime": "^0.0.6", "nullthrows": "^1.1.1" } }, "sha512-Ss0FpBiZDjX2kwhukMDl5sNdYK8T/06IPqxNE4H6PTlRlfs9q11cef13c/xESY/Pm4VCkp1yJUZO3kXzvMxQFA=="], + + "@react-native/community-cli-plugin/metro/metro-transform-worker": ["metro-transform-worker@0.83.7", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/generator": "^7.29.1", "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "flow-enums-runtime": "^0.0.6", "metro": "0.83.7", "metro-babel-transformer": "0.83.7", "metro-cache": "0.83.7", "metro-cache-key": "0.83.7", "metro-minify-terser": "0.83.7", "metro-source-map": "0.83.7", "metro-transform-plugins": "0.83.7", "nullthrows": "^1.1.1" } }, "sha512-UegCo7ygB2fT64mRK2nbAjQVJ1zSwIIHy8d96jJv2nKZFDaViYBiughEdu5HM/Ceq0WN3LZrZk3zhl9aoiLYFw=="], + + "@react-native/community-cli-plugin/metro/ws": ["ws@7.5.10", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": "^5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ=="], + + "@react-native/community-cli-plugin/metro-config/metro-cache": ["metro-cache@0.83.7", "", { "dependencies": { "exponential-backoff": "^3.1.1", "flow-enums-runtime": "^0.0.6", "https-proxy-agent": "^7.0.5", "metro-core": "0.83.7" } }, "sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg=="], + + "@react-native/community-cli-plugin/metro-core/metro-resolver": ["metro-resolver@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6" } }, "sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A=="], + "@react-native/dev-middleware/serve-static/send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], "@sentry/cli/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], @@ -4826,25 +4855,25 @@ "drizzle-kit/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0" } }, "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2" } }, "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0", "@typescript-eslint/utils": "8.59.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3TRiZaQSltGqGeNrJzzr1+8YcEobKH9rHnqIp/1psfKFmhRQDNMGP5hBufanYTGznwShzVLs3Mz+gDN7HkWfXg=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "@typescript-eslint/typescript-estree": "8.59.2", "@typescript-eslint/utils": "8.59.2", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/typescript-estree": "8.59.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-I1R/K7V07XsMJ12Oaxg/O9GfrysGTmCRhvZJBv0RE0NcULMzjqVpR5kRRQjHsz3J/bElU7HwCO7zkqL+MSUz+g=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/utils": ["@typescript-eslint/utils@8.59.2", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.59.2", "@typescript-eslint/types": "8.59.2", "@typescript-eslint/typescript-estree": "8.59.2" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA=="], "eslint-config-universe/@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], "eslint-config-universe/@typescript-eslint/eslint-plugin/ts-api-utils": ["ts-api-utils@2.5.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA=="], - "eslint-config-universe/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0" } }, "sha512-UzR16Ut8IpA3Mc4DbgAShlPPkVm8xXMWafXxB0BocaVRHs8ZGakAxGRskF7FId3sdk9lgGD73GSFaWmWFDE4dg=="], + "eslint-config-universe/@typescript-eslint/parser/@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2" } }, "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg=="], - "eslint-config-universe/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], + "eslint-config-universe/@typescript-eslint/parser/@typescript-eslint/types": ["@typescript-eslint/types@8.59.2", "", {}, "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q=="], - "eslint-config-universe/@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.0", "@typescript-eslint/tsconfig-utils": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw=="], + "eslint-config-universe/@typescript-eslint/parser/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.2", "@typescript-eslint/tsconfig-utils": "8.59.2", "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg=="], - "eslint-config-universe/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.0", "", { "dependencies": { "@typescript-eslint/types": "8.59.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-/uejZt4dSere1bx12WLlPfv8GktzcaDtuJ7s42/HEZ5zGj9oxRaD4bj7qwSunXkf+pbAhFt2zjpHYUiT5lHf0Q=="], + "eslint-config-universe/@typescript-eslint/parser/@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.59.2", "", { "dependencies": { "@typescript-eslint/types": "8.59.2", "eslint-visitor-keys": "^5.0.0" } }, "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA=="], "eslint-plugin-import/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], @@ -4946,7 +4975,7 @@ "miniflare/workerd/@cloudflare/workerd-windows-64": ["@cloudflare/workerd-windows-64@1.20250906.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Q8Qjfs8jGVILnZL6vUpQ90q/8MTCYaGR3d1LGxZMBqte8Vr7xF3KFHPEy7tFs0j0mMjnqCYzlofmPNY+9ZaDRg=="], - "next/postcss/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "next/postcss/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "ora/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], @@ -5020,7 +5049,7 @@ "vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], - "workers-ai-provider/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + "workers-ai-provider/@ai-sdk/provider-utils/nanoid": ["nanoid@3.3.12", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ=="], "wrangler/miniflare/undici": ["undici@7.24.8", "", {}, "sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ=="], @@ -5110,6 +5139,10 @@ "@react-native/codegen/glob/minimatch/brace-expansion": ["brace-expansion@1.1.14", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g=="], + "@react-native/community-cli-plugin/metro/hermes-parser/hermes-estree": ["hermes-estree@0.35.0", "", {}, "sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg=="], + + "@react-native/community-cli-plugin/metro/metro-transform-worker/metro-minify-terser": ["metro-minify-terser@0.83.7", "", { "dependencies": { "flow-enums-runtime": "^0.0.6", "terser": "^5.15.0" } }, "sha512-MfJar2IS4tBRuLb9svwb0Gu5l9BsH+pcRm8eGcEi/wy8MzZinfinh5dFLt2nWkocnulIgtGB5NkFDdbXqMXKhQ=="], + "@react-native/dev-middleware/serve-static/send/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "@react-native/dev-middleware/serve-static/send/fresh": ["fresh@0.5.2", "", {}, "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q=="], @@ -5130,17 +5163,17 @@ "chromium-edge-launcher/rimraf/glob/minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/scope-manager/@typescript-eslint/types": ["@typescript-eslint/types@8.59.2", "", {}, "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/types": ["@typescript-eslint/types@8.59.2", "", {}, "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.0", "@typescript-eslint/tsconfig-utils": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/type-utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.2", "@typescript-eslint/tsconfig-utils": "8.59.2", "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/types": ["@typescript-eslint/types@8.59.2", "", {}, "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.0", "@typescript-eslint/tsconfig-utils": "8.59.0", "@typescript-eslint/types": "8.59.0", "@typescript-eslint/visitor-keys": "8.59.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-O9Re9P1BmBLFJyikRbQpLku/QA3/AueZNO9WePLBwQrvkixTmDe8u76B6CYUAITRl/rHawggEqUGn5QIkVRLMw=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/utils/@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.59.2", "", { "dependencies": { "@typescript-eslint/project-service": "8.59.2", "@typescript-eslint/tsconfig-utils": "8.59.2", "@typescript-eslint/types": "8.59.2", "@typescript-eslint/visitor-keys": "8.59.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg=="], - "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.59.0", "", {}, "sha512-nLzdsT1gdOgFxxxwrlNVUBzSNBEEHJ86bblmk4QAS6stfig7rcJzWKqCyxFy3YRRHXDWEkb2NralA1nOYkkm/A=="], + "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/@typescript-eslint/types": ["@typescript-eslint/types@8.59.2", "", {}, "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q=="], "eslint-config-universe/@typescript-eslint/eslint-plugin/@typescript-eslint/visitor-keys/eslint-visitor-keys": ["eslint-visitor-keys@5.0.1", "", {}, "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA=="], From e49810b8234bb66b1054bb2aed8aa77008857f9f Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 14:41:10 -0600 Subject: [PATCH 13/21] fix(trails): address Copilot review findings - Fix API response shape: search returns plain array, not {trails,hasMore} - Fix bbox destructuring order: [minlon,minlat,maxlon,maxlat] = west/south/east/north - Fix bboxCenter: accept bbox tuple instead of GeoJSON object - Fix apiError: read 'error' field before 'message' to match PackRat API shape - Restrict CORS to allowed origins; exempt OPTIONS from rate limiting - Tighten TRAIL_DETAIL_RE to numeric OSM IDs only (/api/trails/\d+) - Add NEXT_PUBLIC_PACKRAT_API_ORIGIN dev override for apiClient - Add usageCount .min(0) constraint to CatalogItemSchema - Keep import type for CatalogItemSchema/UserSchema (z.infer is type-level) --- apps/trails/lib/apiClient.ts | 5 ++- apps/trails/lib/overpass.ts | 2 +- apps/trails/lib/trailSearch.ts | 36 +++++++----------- apps/trails/lib/useAuth.tsx | 3 +- apps/trails/worker/index.ts | 57 +++++++++++++++++------------ apps/trails/wrangler.jsonc | 5 ++- packages/api/src/schemas/catalog.ts | 2 +- 7 files changed, 59 insertions(+), 51 deletions(-) diff --git a/apps/trails/lib/apiClient.ts b/apps/trails/lib/apiClient.ts index 846c0d727c..c646a5d0d5 100644 --- a/apps/trails/lib/apiClient.ts +++ b/apps/trails/lib/apiClient.ts @@ -10,8 +10,11 @@ import { } from 'trails-app/lib/auth'; // Routes through the same-origin CF Worker proxy (/api/*) so rate limiting applies. +// In local dev without the worker, set NEXT_PUBLIC_PACKRAT_API_ORIGIN to the API URL directly. export const apiClient = createApiClient({ - baseUrl: typeof window !== 'undefined' ? window.location.origin : '', + baseUrl: + process.env.NEXT_PUBLIC_PACKRAT_API_ORIGIN ?? + (typeof window !== 'undefined' ? window.location.origin : ''), auth: { getAccessToken, getRefreshToken, diff --git a/apps/trails/lib/overpass.ts b/apps/trails/lib/overpass.ts index 6d2374b23c..dc1ba28716 100644 --- a/apps/trails/lib/overpass.ts +++ b/apps/trails/lib/overpass.ts @@ -24,7 +24,7 @@ export async function loadNearbyTrails( const summary = toTrailSummary(el); let center: [number, number] | null = null; if (summary.bbox) { - const [south, west, north, east] = summary.bbox; + const [west, south, east, north] = summary.bbox; center = [(south + north) / 2, (west + east) / 2]; } return { ...summary, center }; diff --git a/apps/trails/lib/trailSearch.ts b/apps/trails/lib/trailSearch.ts index fcf990c931..664452869e 100644 --- a/apps/trails/lib/trailSearch.ts +++ b/apps/trails/lib/trailSearch.ts @@ -19,10 +19,7 @@ export interface TrailSearchResult { hasMore: boolean; } -interface ApiBbox { - coordinates?: number[][][][]; -} - +// API returns toTrailSummary shape: bbox is [minlon, minlat, maxlon, maxlat] (west/south/east/north) interface ApiTrail { osmId: string; name: string | null; @@ -31,24 +28,17 @@ interface ApiTrail { distance: string | null; difficulty: string | null; description: string | null; - bbox: ApiBbox | null; + bbox: [number, number, number, number] | null; } function bboxCenter(bbox: ApiTrail['bbox']): [number, number] | null { - if (!bbox?.coordinates?.[0]) return null; - const ring = bbox.coordinates[0]; - if (!ring) return null; - const lons = ring.flatMap((p) => (typeof p[0] === 'number' ? [p[0]] : [])); - const lats = ring.flatMap((p) => (typeof p[1] === 'number' ? [p[1]] : [])); - if (lons.length === 0 || lats.length === 0) return null; - const minLon = Math.min(...lons); - const maxLon = Math.max(...lons); - const minLat = Math.min(...lats); - const maxLat = Math.max(...lats); - return [(minLat + maxLat) / 2, (minLon + maxLon) / 2]; + if (!bbox) return null; + const [west, south, east, north] = bbox; + return [(south + north) / 2, (west + east) / 2]; } export async function searchTrails(params: TrailSearchParams): Promise { + const limit = params.limit ?? 20; const { data, error, status } = await apiClient.trails.search.get({ query: { q: params.q, @@ -56,19 +46,21 @@ export async function searchTrails(params: TrailSearchParams): Promise ({ + // API returns a plain array of trail summaries + const rawTrails = data as unknown as ApiTrail[]; + const trails: TrailSummaryWithCoords[] = rawTrails.map((t) => ({ osmId: t.osmId, name: t.name, sport: t.sport, @@ -76,9 +68,9 @@ export async function searchTrails(params: TrailSearchParams): Promise= limit }; } diff --git a/apps/trails/lib/useAuth.tsx b/apps/trails/lib/useAuth.tsx index 35b54e4e74..3b7bf4abef 100644 --- a/apps/trails/lib/useAuth.tsx +++ b/apps/trails/lib/useAuth.tsx @@ -36,7 +36,8 @@ interface AuthActions { const AuthContext = createContext<(AuthState & AuthActions) | null>(null); function apiError(error: unknown, fallback: string): Error { - const msg = asStringRecord(error).message; + const rec = asStringRecord(error); + const msg = rec.error ?? rec.message; return new Error(msg ?? fallback); } diff --git a/apps/trails/worker/index.ts b/apps/trails/worker/index.ts index 49b3e2deda..46ad44b66e 100644 --- a/apps/trails/worker/index.ts +++ b/apps/trails/worker/index.ts @@ -4,23 +4,40 @@ interface Env { PACKRAT_API_BASE_URL: string; } -const TRAIL_DETAIL_RE = /^\/api\/trails\/[^/]+$/; - -const CORS_HEADERS = { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', -}; - -function corsResponse(status: number, body: string): Response { - return new Response(body, { - status, - headers: { ...CORS_HEADERS, 'Content-Type': 'application/json' }, - }); +// Only cache responses for individual trail detail lookups (numeric OSM IDs). +// Excludes /api/trails/search and any other non-ID routes. +const TRAIL_DETAIL_RE = /^\/api\/trails\/\d+$/; +const LOCALHOST_RE = /^https?:\/\/localhost(:\d+)?$/; + +const ALLOWED_ORIGINS = new Set([ + 'https://trails.packratai.com', + 'https://staging.trails.packratai.com', +]); + +function corsHeaders(origin: string | null): Record { + const allowed = + origin !== null && (ALLOWED_ORIGINS.has(origin) || LOCALHOST_RE.test(origin)) ? origin : null; + if (!allowed) return {}; + return { + 'Access-Control-Allow-Origin': allowed, + 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers': 'Content-Type, Authorization', + Vary: 'Origin', + }; +} + +function jsonError(status: number, body: string): Response { + return new Response(body, { status, headers: { 'Content-Type': 'application/json' } }); } async function proxyToApi(request: Request, env: Env): Promise { const url = new URL(request.url); + const origin = request.headers.get('Origin'); + + // Handle CORS preflight before rate limiting so OPTIONS never consumes quota + if (request.method === 'OPTIONS') { + return new Response(null, { status: 204, headers: corsHeaders(origin) }); + } // Rate limit by IP if (env.RATE_LIMITER) { @@ -30,18 +47,13 @@ async function proxyToApi(request: Request, env: Env): Promise { 'unknown'; const { success } = await env.RATE_LIMITER.limit({ key: ip }); if (!success) { - return corsResponse( + return jsonError( 429, JSON.stringify({ error: 'Too many requests. Please try again in a moment.' }), ); } } - // Handle CORS preflight - if (request.method === 'OPTIONS') { - return new Response(null, { status: 204, headers: CORS_HEADERS }); - } - // Build upstream URL const upstream = new URL(url.pathname + url.search, env.PACKRAT_API_BASE_URL); @@ -56,20 +68,19 @@ async function proxyToApi(request: Request, env: Env): Promise { const response = await fetch(proxyRequest); const responseBody = await response.text(); - // Add CORS headers to the proxied response const headers = new Headers(response.headers); - for (const [key, value] of Object.entries(CORS_HEADERS)) { + for (const [key, value] of Object.entries(corsHeaders(origin))) { headers.set(key, value); } - // Cache trail detail responses at edge (~1 hour TTL for non-search requests) + // Cache trail detail responses at edge (~1 hour TTL); never cache search results if (TRAIL_DETAIL_RE.test(url.pathname) && request.method === 'GET') { headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=600'); } return new Response(responseBody, { status: response.status, headers }); } catch { - return corsResponse(502, JSON.stringify({ error: 'API unavailable. Please try again later.' })); + return jsonError(502, JSON.stringify({ error: 'API unavailable. Please try again later.' })); } } diff --git a/apps/trails/wrangler.jsonc b/apps/trails/wrangler.jsonc index 9d99f069d5..01e89e501b 100644 --- a/apps/trails/wrangler.jsonc +++ b/apps/trails/wrangler.jsonc @@ -9,8 +9,9 @@ "not_found_handling": "404-page" }, // Rate limiting: 60 requests per IP per 60 seconds - // Create namespace: wrangler rate-limit create --simple --limit 60 --period 60 - // Then replace namespace_id below with the returned ID + // Before deploying, create a namespace and set the real ID: + // wrangler rate-limit create --simple --limit 60 --period 60 + // The worker handles RATE_LIMITER being absent gracefully (no limiting in local dev). "rate_limiting": [ { "binding": "RATE_LIMITER", diff --git a/packages/api/src/schemas/catalog.ts b/packages/api/src/schemas/catalog.ts index e6477c6b7c..3a6d5b78d9 100644 --- a/packages/api/src/schemas/catalog.ts +++ b/packages/api/src/schemas/catalog.ts @@ -94,7 +94,7 @@ export const CatalogItemSchema = z.object({ ) .nullable() .optional(), - usageCount: z.number().int().optional(), + usageCount: z.number().int().min(0).optional(), createdAt: z.string().datetime(), updatedAt: z.string().datetime(), }); From ba3248262ed7456a1adaf45776fb807767a8d20c Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 14:44:41 -0600 Subject: [PATCH 14/21] fix(trails): replace raw process.env with typed env shim Adds apps/trails/lib/env.ts following the same pattern as apps/admin/lib/env.ts so NEXT_PUBLIC_PACKRAT_API_ORIGIN is parsed through Zod once at module load. Unblocks no-raw-process-env pre-push check. --- apps/trails/lib/apiClient.ts | 3 ++- apps/trails/lib/env.ts | 20 ++++++++++++++++++++ packages/env/scripts/no-raw-process-env.ts | 2 ++ 3 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 apps/trails/lib/env.ts diff --git a/apps/trails/lib/apiClient.ts b/apps/trails/lib/apiClient.ts index c646a5d0d5..73f0401c4c 100644 --- a/apps/trails/lib/apiClient.ts +++ b/apps/trails/lib/apiClient.ts @@ -8,12 +8,13 @@ import { getRefreshToken, setTokens, } from 'trails-app/lib/auth'; +import { trailsEnv } from 'trails-app/lib/env'; // Routes through the same-origin CF Worker proxy (/api/*) so rate limiting applies. // In local dev without the worker, set NEXT_PUBLIC_PACKRAT_API_ORIGIN to the API URL directly. export const apiClient = createApiClient({ baseUrl: - process.env.NEXT_PUBLIC_PACKRAT_API_ORIGIN ?? + trailsEnv.NEXT_PUBLIC_PACKRAT_API_ORIGIN ?? (typeof window !== 'undefined' ? window.location.origin : ''), auth: { getAccessToken, diff --git a/apps/trails/lib/env.ts b/apps/trails/lib/env.ts new file mode 100644 index 0000000000..1e40cb0976 --- /dev/null +++ b/apps/trails/lib/env.ts @@ -0,0 +1,20 @@ +/** + * Trails app environment shim. + * Parses `process.env` once at module load using Zod and exports a typed result. + * + * Adding a new variable: declare it on `trailsEnvSchema`, mark it + * `.optional()` unless every caller genuinely requires it. + */ + +import { z } from 'zod'; + +const trailsEnvSchema = z.object({ + // Dev override: point the api client at a local server instead of the CF Worker proxy. + NEXT_PUBLIC_PACKRAT_API_ORIGIN: z.string().url().optional(), +}); + +export type TrailsEnv = z.infer; + +export const trailsEnv = trailsEnvSchema.parse({ + NEXT_PUBLIC_PACKRAT_API_ORIGIN: process.env.NEXT_PUBLIC_PACKRAT_API_ORIGIN, +}); diff --git a/packages/env/scripts/no-raw-process-env.ts b/packages/env/scripts/no-raw-process-env.ts index 6bab66996c..5da61c7005 100644 --- a/packages/env/scripts/no-raw-process-env.ts +++ b/packages/env/scripts/no-raw-process-env.ts @@ -52,6 +52,8 @@ const ALLOWED: string[] = [ 'packages/api/src/utils/__tests__/', // Admin env shim — parses process.env once at module load 'apps/admin/lib/env.ts', + // Trails app env shim — parses process.env once at module load + 'apps/trails/lib/env.ts', ]; // Directories to skip entirely From 347d82ee2513691755b9988f64cf5f4b6fbd64fe Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 14:45:40 -0600 Subject: [PATCH 15/21] fix(trails): replace unsafe cast with fromZod schema validation Defines ApiTrailSchema using Zod and parses the raw API response through fromZod() instead of a double as-cast, satisfying check:casts:strict. --- apps/trails/lib/trailSearch.ts | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/apps/trails/lib/trailSearch.ts b/apps/trails/lib/trailSearch.ts index 664452869e..ee73e158e9 100644 --- a/apps/trails/lib/trailSearch.ts +++ b/apps/trails/lib/trailSearch.ts @@ -1,6 +1,7 @@ -import { asStringRecord } from '@packrat/guards'; +import { asStringRecord, fromZod } from '@packrat/guards'; import { AuthExpiredError, apiClient } from 'trails-app/lib/apiClient'; import type { TrailSummaryWithCoords } from 'trails-app/lib/overpass'; +import { z } from 'zod'; export { AuthExpiredError } from 'trails-app/lib/apiClient'; @@ -20,16 +21,20 @@ export interface TrailSearchResult { } // API returns toTrailSummary shape: bbox is [minlon, minlat, maxlon, maxlat] (west/south/east/north) -interface ApiTrail { - osmId: string; - name: string | null; - sport: string | null; - network: string | null; - distance: string | null; - difficulty: string | null; - description: string | null; - bbox: [number, number, number, number] | null; -} +const ApiTrailSchema = z.object({ + osmId: z.string(), + name: z.string().nullable(), + sport: z.string().nullable(), + network: z.string().nullable(), + distance: z.string().nullable(), + difficulty: z.string().nullable(), + description: z.string().nullable(), + bbox: z.tuple([z.number(), z.number(), z.number(), z.number()]).nullable(), +}); + +type ApiTrail = z.infer; + +const parseApiTrails = fromZod(z.array(ApiTrailSchema)); function bboxCenter(bbox: ApiTrail['bbox']): [number, number] | null { if (!bbox) return null; @@ -58,8 +63,8 @@ export async function searchTrails(params: TrailSearchParams): Promise ({ osmId: t.osmId, name: t.name, From 8a558939e303f384e50cd3f5213d5c779b587469 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 17:33:19 -0600 Subject: [PATCH 16/21] fix(trails): correct sport/offset types and pin nativewindui to 2.0.3 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - sport: string → OsmSport in TrailSearchParams (matches API enum) - offset: pass ?? 0 fallback so undefined doesn't fail required number type - Pin @packrat-ai/nativewindui to 2.0.3 via root overrides; 2.0.6 has a type regression against react-native 0.81 autocapitalize types --- apps/trails/lib/trailSearch.ts | 5 +++-- bun.lock | 3 ++- package.json | 1 + 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/trails/lib/trailSearch.ts b/apps/trails/lib/trailSearch.ts index ee73e158e9..0e9f2b477f 100644 --- a/apps/trails/lib/trailSearch.ts +++ b/apps/trails/lib/trailSearch.ts @@ -1,4 +1,5 @@ import { asStringRecord, fromZod } from '@packrat/guards'; +import type { OsmSport } from '@packrat/overpass'; import { AuthExpiredError, apiClient } from 'trails-app/lib/apiClient'; import type { TrailSummaryWithCoords } from 'trails-app/lib/overpass'; import { z } from 'zod'; @@ -10,7 +11,7 @@ export interface TrailSearchParams { lat?: number; lon?: number; radius?: number; - sport?: string; + sport?: OsmSport; limit?: number; offset?: number; } @@ -52,7 +53,7 @@ export async function searchTrails(params: TrailSearchParams): Promise=15.0.0", "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "^8.4.0", "@react-native-community/slider": "^5.0.0", "@react-native-picker/picker": "^2.11.0", "@react-native-segmented-control/segmented-control": "^2.5.0", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.0.4", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@shopify/flash-list": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo-blur": "~55.0.0", "expo-device": "~55.0.0", "expo-glass-effect": "~55.0.0", "expo-haptics": "~55.0.0", "expo-image": "~55.0.0", "expo-linear-gradient": "~55.0.0", "expo-navigation-bar": "~55.0.0", "expo-router": "~55.0.0", "expo-symbols": "~55.0.0", "nativewind": "^4.2.3", "react": ">=19.2.0", "react-native": ">=0.83.0", "react-native-keyboard-controller": "^1.21.0", "react-native-reanimated": ">=4.2.0", "react-native-safe-area-context": ">=5.6.0", "react-native-screens": ">=4.23.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-AB8MfYtVajR8i1MyQUeeJ7QuoHpkeGPqKjmv4Gu5+FZRDM1LNqtf3YTfuFEEoOx7UJc7/5tEWsDo8hOeOQBzCg=="], + "@packrat-ai/nativewindui": ["@packrat-ai/nativewindui@2.0.3", "https://npm.pkg.github.com/download/@packrat-ai/nativewindui/2.0.3/6d1d9364d32c4a145cc9fc49a73744b553db5e14", { "peerDependencies": { "@expo/vector-icons": ">=15.0.0", "@gorhom/bottom-sheet": "^5.1.2", "@react-native-community/datetimepicker": "^8.4.0", "@react-native-community/slider": "^5.0.0", "@react-native-picker/picker": "^2.11.0", "@react-native-segmented-control/segmented-control": "^2.5.0", "@react-navigation/drawer": "^7.1.1", "@react-navigation/elements": "^2.3.1", "@react-navigation/native": "^7.0.14", "@rn-primitives/alert-dialog": "^1.1.0", "@rn-primitives/avatar": "^1.0.4", "@rn-primitives/checkbox": "^1.1.0", "@rn-primitives/context-menu": "^1.1.0", "@rn-primitives/dropdown-menu": "^1.1.0", "@rn-primitives/hooks": "^1.1.0", "@rn-primitives/portal": "^1.1.0", "@rn-primitives/slot": "^1.1.0", "@shopify/flash-list": "^2.0.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "expo-blur": "~15.0.8", "expo-device": "~8.0.0", "expo-glass-effect": "*", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", "expo-linear-gradient": "~15.0.8", "expo-navigation-bar": "~5.0.10", "expo-router": "~6.0.23", "expo-symbols": "~1.0.8", "nativewind": "^4.2.3", "react": ">=19.0.0", "react-native": ">=0.79.0", "react-native-keyboard-controller": "^1.16.7", "react-native-reanimated": ">=3.17.0", "react-native-safe-area-context": ">=5.4.0", "react-native-screens": ">=4.11.0", "react-native-uitextview": "^1.1.4", "rn-icon-mapper": "^0.0.1", "tailwind-merge": "^2.2.1" } }, "sha512-C0PzEmKNqc7KS6DcxRN6hOtyIpkK6xGLO0I3x6eKBtunGSPseQWz/JEdsQW2i42xRVZN3r83loYb3v7dVUDTFg=="], "@packrat/analytics": ["@packrat/analytics@workspace:packages/analytics"], diff --git a/package.json b/package.json index 91f13a9c89..1057b44099 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ }, "overrides": { "@sinclair/typebox": "^0.34.15", + "@packrat-ai/nativewindui": "2.0.3", "elysia": "^1.4.0", "expo-sqlite": "~16.0.10" }, From 17ad75c62e18d94092a6788751c32e432a6555b3 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 17:33:31 -0600 Subject: [PATCH 17/21] chore: sort package.json overrides --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1057b44099..633fea47a6 100644 --- a/package.json +++ b/package.json @@ -49,8 +49,8 @@ "trails": "bun run --cwd apps/trails dev" }, "overrides": { - "@sinclair/typebox": "^0.34.15", "@packrat-ai/nativewindui": "2.0.3", + "@sinclair/typebox": "^0.34.15", "elysia": "^1.4.0", "expo-sqlite": "~16.0.10" }, From fefc42d2fe0921ce2af8a8c50b3ebc0405adfbe8 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 21:07:09 -0600 Subject: [PATCH 18/21] =?UTF-8?q?fix(trails):=20address=20review=20comment?= =?UTF-8?q?s=20=E2=80=94=20worker=20security,=20auth=20guards,=20tsconfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - worker: drop X-Forwarded-For fallback (client-spoofable); use CF-Connecting-IP only - worker: include CORS headers on 429 rate-limit response so browsers see the actual error - auth: add typeof-window guards to setTokens/clearTokens/setUser/clearUser mutators - apiClient: clear session when counterpart token is absent on refresh callbacks - TrailMap: add cancellation flag to async marker-update effect to prevent stale layers - TrailsPage: remove redundant setMapState inside if(!coords) block (dead code) - tsconfig: add noUncheckedIndexedAccess + ESNext target + @packrat/api bare alias - next.config: remove ignoreBuildErrors/ignoreDuringBuilds; rely on bun check-types gate 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/trails/components/TrailMap.tsx | 6 ++++++ apps/trails/components/TrailsPage.tsx | 1 - apps/trails/lib/apiClient.ts | 8 ++++++++ apps/trails/lib/auth.ts | 4 ++++ apps/trails/next.config.mjs | 6 ------ apps/trails/tsconfig.json | 4 +++- apps/trails/worker/index.ts | 9 +++------ 7 files changed, 24 insertions(+), 14 deletions(-) diff --git a/apps/trails/components/TrailMap.tsx b/apps/trails/components/TrailMap.tsx index ad5401aac1..a79dea792a 100644 --- a/apps/trails/components/TrailMap.tsx +++ b/apps/trails/components/TrailMap.tsx @@ -76,8 +76,10 @@ export function TrailMap({ center, trails, selectedOsmId, onTrailClick }: TrailM if (!markersRef.current) return; const group = markersRef.current; group.clearLayers(); + let cancelled = false; import('leaflet').then(({ default: L }) => { + if (cancelled) return; for (const trail of trails) { if (!trail.center) continue; const isSelected = trail.osmId === selectedOsmId; @@ -96,6 +98,10 @@ export function TrailMap({ center, trails, selectedOsmId, onTrailClick }: TrailM group.addLayer(marker); } }); + + return () => { + cancelled = true; + }; }, [trails, selectedOsmId, onTrailClick]); return
; diff --git a/apps/trails/components/TrailsPage.tsx b/apps/trails/components/TrailsPage.tsx index 8542d9ffb3..1c6785fe71 100644 --- a/apps/trails/components/TrailsPage.tsx +++ b/apps/trails/components/TrailsPage.tsx @@ -55,7 +55,6 @@ export function TrailsPage() { setMapState({ status: 'idle', center }); if (!coords) { - setMapState({ status: 'idle', center: DEFAULT_CENTER }); return; } diff --git a/apps/trails/lib/apiClient.ts b/apps/trails/lib/apiClient.ts index 73f0401c4c..07bad196cc 100644 --- a/apps/trails/lib/apiClient.ts +++ b/apps/trails/lib/apiClient.ts @@ -22,10 +22,18 @@ export const apiClient = createApiClient({ onAccessTokenRefreshed: (token) => { const refresh = getRefreshToken(); if (refresh) setTokens(token, refresh); + else { + clearTokens(); + clearUser(); + } }, onRefreshTokenRefreshed: (token) => { const access = getAccessToken(); if (access) setTokens(access, token); + else { + clearTokens(); + clearUser(); + } }, onNeedsReauth: () => { clearTokens(); diff --git a/apps/trails/lib/auth.ts b/apps/trails/lib/auth.ts index e906df5228..9865530822 100644 --- a/apps/trails/lib/auth.ts +++ b/apps/trails/lib/auth.ts @@ -30,11 +30,13 @@ export function getRefreshToken(): string | null { } export function setTokens(accessToken: string, refreshToken: string): void { + if (typeof window === 'undefined') return; localStorage.setItem(ACCESS_KEY, accessToken); localStorage.setItem(REFRESH_KEY, refreshToken); } export function clearTokens(): void { + if (typeof window === 'undefined') return; localStorage.removeItem(ACCESS_KEY); localStorage.removeItem(REFRESH_KEY); } @@ -49,6 +51,7 @@ export const UserInfoSchema = z.object({ export type UserInfo = z.infer; export function setUser(user: UserInfo): void { + if (typeof window === 'undefined') return; localStorage.setItem('user', JSON.stringify(user)); } @@ -63,5 +66,6 @@ export function getUser(): UserInfo | null { } export function clearUser(): void { + if (typeof window === 'undefined') return; localStorage.removeItem('user'); } diff --git a/apps/trails/next.config.mjs b/apps/trails/next.config.mjs index 37d7999b3e..0384b85d1f 100644 --- a/apps/trails/next.config.mjs +++ b/apps/trails/next.config.mjs @@ -1,12 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { output: 'export', - eslint: { - ignoreDuringBuilds: true, - }, - typescript: { - ignoreBuildErrors: true, - }, images: { unoptimized: true, }, diff --git a/apps/trails/tsconfig.json b/apps/trails/tsconfig.json index bc1eab1397..33021eafd1 100644 --- a/apps/trails/tsconfig.json +++ b/apps/trails/tsconfig.json @@ -2,9 +2,10 @@ "compilerOptions": { "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, - "target": "ES6", + "target": "ESNext", "skipLibCheck": true, "strict": true, + "noUncheckedIndexedAccess": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", @@ -20,6 +21,7 @@ ], "paths": { "trails-app/*": ["./*"], + "@packrat/api": ["../../packages/api/src/index.ts"], "@packrat/api/*": ["../../packages/api/src/*"], "@packrat/overpass": ["../../packages/overpass/src/index.ts"], "@packrat/overpass/*": ["../../packages/overpass/src/*"], diff --git a/apps/trails/worker/index.ts b/apps/trails/worker/index.ts index 46ad44b66e..40a6aff181 100644 --- a/apps/trails/worker/index.ts +++ b/apps/trails/worker/index.ts @@ -41,15 +41,12 @@ async function proxyToApi(request: Request, env: Env): Promise { // Rate limit by IP if (env.RATE_LIMITER) { - const ip = - request.headers.get('CF-Connecting-IP') ?? - request.headers.get('X-Forwarded-For') ?? - 'unknown'; + const ip = request.headers.get('CF-Connecting-IP') ?? 'unknown'; const { success } = await env.RATE_LIMITER.limit({ key: ip }); if (!success) { - return jsonError( - 429, + return new Response( JSON.stringify({ error: 'Too many requests. Please try again in a moment.' }), + { status: 429, headers: { 'Content-Type': 'application/json', ...corsHeaders(origin) } }, ); } } From b0d4b2235b18d76915ccfb4f7ed7f8211542f2c9 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 21:12:26 -0600 Subject: [PATCH 19/21] feat(app): introduce @packrat/app with shared browser/storage utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creates packages/app with safeLocalStorage and safeSessionStorage helpers that include SSR guards, eliminating the repeated typeof-window checks scattered across apps. Removes 9 guard duplicates from trails auth.ts and 2 from admin auth.ts. apps/trails/lib/auth.ts and apps/admin/lib/auth.ts now delegate all storage access through safeLocalStorage/safeSessionStorage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/admin/lib/auth.ts | 9 +++++---- apps/admin/package.json | 1 + apps/admin/tsconfig.json | 2 ++ apps/trails/lib/auth.ts | 26 ++++++++++---------------- apps/trails/package.json | 1 + apps/trails/tsconfig.json | 2 ++ bun.lock | 8 ++++++++ packages/app/package.json | 12 ++++++++++++ packages/app/src/browser.ts | 31 +++++++++++++++++++++++++++++++ packages/app/src/index.ts | 1 + tsconfig.json | 2 ++ 11 files changed, 75 insertions(+), 20 deletions(-) create mode 100644 packages/app/package.json create mode 100644 packages/app/src/browser.ts create mode 100644 packages/app/src/index.ts diff --git a/apps/admin/lib/auth.ts b/apps/admin/lib/auth.ts index ec8ecee1b8..76dfee73d5 100644 --- a/apps/admin/lib/auth.ts +++ b/apps/admin/lib/auth.ts @@ -1,19 +1,20 @@ +import { safeSessionStorage } from '@packrat/app/browser'; + const TOKEN_KEY = 'packrat_admin_token'; /** Returns the stored admin JWT, or null if not logged in. */ export function getStoredToken(): string | null { - if (typeof window === 'undefined') return null; - return sessionStorage.getItem(TOKEN_KEY); + return safeSessionStorage.getItem(TOKEN_KEY); } /** Persist a short-lived admin JWT for the session. */ export function storeToken(token: string): void { - sessionStorage.setItem(TOKEN_KEY, token); + safeSessionStorage.setItem(TOKEN_KEY, token); } /** Remove the token (logout). */ export function clearToken(): void { - if (typeof window !== 'undefined') sessionStorage.removeItem(TOKEN_KEY); + safeSessionStorage.removeItem(TOKEN_KEY); } /** Returns an Authorization header object, or empty object if not logged in. */ diff --git a/apps/admin/package.json b/apps/admin/package.json index 739c3d5648..f6ae4f75a3 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -13,6 +13,7 @@ "dependencies": { "@elysiajs/eden": "catalog:", "@packrat/api-client": "workspace:*", + "@packrat/app": "workspace:*", "@packrat/guards": "workspace:*", "@packrat/web-ui": "workspace:*", "@radix-ui/react-alert-dialog": "catalog:", diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json index 18ba6c4f4b..ba8a263c7e 100644 --- a/apps/admin/tsconfig.json +++ b/apps/admin/tsconfig.json @@ -19,6 +19,8 @@ } ], "paths": { + "@packrat/app": ["../../packages/app/src/index.ts"], + "@packrat/app/*": ["../../packages/app/src/*"], "admin-app/*": ["./*"], "@packrat/api/*": ["../../packages/api/src/*"], "@packrat/guards": ["../../packages/guards/src"], diff --git a/apps/trails/lib/auth.ts b/apps/trails/lib/auth.ts index 9865530822..11198e198a 100644 --- a/apps/trails/lib/auth.ts +++ b/apps/trails/lib/auth.ts @@ -2,6 +2,7 @@ // atomWithStorage JSON-encodes values; raw JWTs may also be written directly. // Always use these helpers — never read localStorage tokens raw. +import { safeLocalStorage } from '@packrat/app/browser'; import { fromZod, isString } from '@packrat/guards'; import z from 'zod'; @@ -20,25 +21,21 @@ function parseToken(raw: string | null): string | null { } export function getAccessToken(): string | null { - if (typeof window === 'undefined') return null; - return parseToken(localStorage.getItem(ACCESS_KEY)); + return parseToken(safeLocalStorage.getItem(ACCESS_KEY)); } export function getRefreshToken(): string | null { - if (typeof window === 'undefined') return null; - return parseToken(localStorage.getItem(REFRESH_KEY)); + return parseToken(safeLocalStorage.getItem(REFRESH_KEY)); } export function setTokens(accessToken: string, refreshToken: string): void { - if (typeof window === 'undefined') return; - localStorage.setItem(ACCESS_KEY, accessToken); - localStorage.setItem(REFRESH_KEY, refreshToken); + safeLocalStorage.setItem(ACCESS_KEY, accessToken); + safeLocalStorage.setItem(REFRESH_KEY, refreshToken); } export function clearTokens(): void { - if (typeof window === 'undefined') return; - localStorage.removeItem(ACCESS_KEY); - localStorage.removeItem(REFRESH_KEY); + safeLocalStorage.removeItem(ACCESS_KEY); + safeLocalStorage.removeItem(REFRESH_KEY); } export const UserInfoSchema = z.object({ @@ -51,14 +48,12 @@ export const UserInfoSchema = z.object({ export type UserInfo = z.infer; export function setUser(user: UserInfo): void { - if (typeof window === 'undefined') return; - localStorage.setItem('user', JSON.stringify(user)); + safeLocalStorage.setItem('user', JSON.stringify(user)); } export function getUser(): UserInfo | null { - if (typeof window === 'undefined') return null; try { - const raw = localStorage.getItem('user'); + const raw = safeLocalStorage.getItem('user'); return raw ? (fromZod(UserInfoSchema)(JSON.parse(raw)) ?? null) : null; } catch { return null; @@ -66,6 +61,5 @@ export function getUser(): UserInfo | null { } export function clearUser(): void { - if (typeof window === 'undefined') return; - localStorage.removeItem('user'); + safeLocalStorage.removeItem('user'); } diff --git a/apps/trails/package.json b/apps/trails/package.json index 7f8dad959d..2302376f95 100644 --- a/apps/trails/package.json +++ b/apps/trails/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@packrat/api-client": "workspace:*", + "@packrat/app": "workspace:*", "@packrat/guards": "workspace:*", "@packrat/overpass": "workspace:*", "@packrat/web-ui": "workspace:*", diff --git a/apps/trails/tsconfig.json b/apps/trails/tsconfig.json index 33021eafd1..7ff4df4551 100644 --- a/apps/trails/tsconfig.json +++ b/apps/trails/tsconfig.json @@ -20,6 +20,8 @@ } ], "paths": { + "@packrat/app": ["../../packages/app/src/index.ts"], + "@packrat/app/*": ["../../packages/app/src/*"], "trails-app/*": ["./*"], "@packrat/api": ["../../packages/api/src/index.ts"], "@packrat/api/*": ["../../packages/api/src/*"], diff --git a/bun.lock b/bun.lock index 221211f7f5..69b26deb20 100644 --- a/bun.lock +++ b/bun.lock @@ -23,6 +23,7 @@ "dependencies": { "@elysiajs/eden": "catalog:", "@packrat/api-client": "workspace:*", + "@packrat/app": "workspace:*", "@packrat/guards": "workspace:*", "@packrat/web-ui": "workspace:*", "@radix-ui/react-alert-dialog": "catalog:", @@ -343,6 +344,7 @@ "version": "2.0.24", "dependencies": { "@packrat/api-client": "workspace:*", + "@packrat/app": "workspace:*", "@packrat/guards": "workspace:*", "@packrat/overpass": "workspace:*", "@packrat/web-ui": "workspace:*", @@ -468,6 +470,10 @@ "elysia", ], }, + "packages/app": { + "name": "@packrat/app", + "version": "2.0.24", + }, "packages/checks": { "name": "@packrat/checks", "version": "2.0.24", @@ -1338,6 +1344,8 @@ "@packrat/api-client": ["@packrat/api-client@workspace:packages/api-client"], + "@packrat/app": ["@packrat/app@workspace:packages/app"], + "@packrat/checks": ["@packrat/checks@workspace:packages/checks"], "@packrat/cli": ["@packrat/cli@workspace:packages/cli"], diff --git a/packages/app/package.json b/packages/app/package.json new file mode 100644 index 0000000000..f88aa5f6bd --- /dev/null +++ b/packages/app/package.json @@ -0,0 +1,12 @@ +{ + "name": "@packrat/app", + "version": "2.0.24", + "private": true, + "type": "module", + "exports": { + ".": "./src/index.ts", + "./*": "./src/*.ts" + }, + "main": "./src/index.ts", + "types": "./src/index.ts" +} diff --git a/packages/app/src/browser.ts b/packages/app/src/browser.ts new file mode 100644 index 0000000000..b5f1d7b8de --- /dev/null +++ b/packages/app/src/browser.ts @@ -0,0 +1,31 @@ +export const isBrowser = (): boolean => typeof window !== 'undefined'; + +export const safeLocalStorage = { + getItem(key: string): string | null { + if (!isBrowser()) return null; + return localStorage.getItem(key); + }, + setItem(key: string, value: string): void { + if (!isBrowser()) return; + localStorage.setItem(key, value); + }, + removeItem(key: string): void { + if (!isBrowser()) return; + localStorage.removeItem(key); + }, +}; + +export const safeSessionStorage = { + getItem(key: string): string | null { + if (!isBrowser()) return null; + return sessionStorage.getItem(key); + }, + setItem(key: string, value: string): void { + if (!isBrowser()) return; + sessionStorage.setItem(key, value); + }, + removeItem(key: string): void { + if (!isBrowser()) return; + sessionStorage.removeItem(key); + }, +}; diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts new file mode 100644 index 0000000000..6346d4753f --- /dev/null +++ b/packages/app/src/index.ts @@ -0,0 +1 @@ +export * from './browser'; diff --git a/tsconfig.json b/tsconfig.json index d3c7dce925..fafb03295b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,8 @@ "expo-app/*": ["./apps/expo/*"], "app/*": ["./packages/app/*"], "config/*": ["./packages/config/*"], + "@packrat/app": ["./packages/app/src/index.ts"], + "@packrat/app/*": ["./packages/app/src/*"], "@packrat/api": ["./packages/api/src/index.ts"], "@packrat/api/*": ["./packages/api/src/*"], "@packrat/api-client": ["./packages/api-client/src/index.ts"], From ce503a3dbbf2ce2816588bfacb05ad47b97b6c8e Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 21:31:46 -0600 Subject: [PATCH 20/21] =?UTF-8?q?chore(trails):=20remove=20CF=20Worker=20p?= =?UTF-8?q?roxy=20=E2=80=94=20api-client=20talks=20directly=20to=20PackRat?= =?UTF-8?q?=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The worker was a same-origin proxy adding rate limiting and CORS, but the PackRat API already handles both. Pointing @packrat/api-client straight at the API via NEXT_PUBLIC_PACKRAT_API_ORIGIN (defaults to api.packratai.com) eliminates the extra deployment layer. Removes: worker/index.ts, wrangler.jsonc, @cloudflare/workers-types, wrangler dep 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/trails/lib/apiClient.ts | 6 +-- apps/trails/lib/env.ts | 11 +---- apps/trails/package.json | 5 +- apps/trails/worker/index.ts | 94 ------------------------------------ apps/trails/wrangler.jsonc | 29 ----------- bun.lock | 2 - 6 files changed, 3 insertions(+), 144 deletions(-) delete mode 100644 apps/trails/worker/index.ts delete mode 100644 apps/trails/wrangler.jsonc diff --git a/apps/trails/lib/apiClient.ts b/apps/trails/lib/apiClient.ts index 07bad196cc..f01fec991d 100644 --- a/apps/trails/lib/apiClient.ts +++ b/apps/trails/lib/apiClient.ts @@ -10,12 +10,8 @@ import { } from 'trails-app/lib/auth'; import { trailsEnv } from 'trails-app/lib/env'; -// Routes through the same-origin CF Worker proxy (/api/*) so rate limiting applies. -// In local dev without the worker, set NEXT_PUBLIC_PACKRAT_API_ORIGIN to the API URL directly. export const apiClient = createApiClient({ - baseUrl: - trailsEnv.NEXT_PUBLIC_PACKRAT_API_ORIGIN ?? - (typeof window !== 'undefined' ? window.location.origin : ''), + baseUrl: trailsEnv.NEXT_PUBLIC_PACKRAT_API_ORIGIN, auth: { getAccessToken, getRefreshToken, diff --git a/apps/trails/lib/env.ts b/apps/trails/lib/env.ts index 1e40cb0976..582d555d45 100644 --- a/apps/trails/lib/env.ts +++ b/apps/trails/lib/env.ts @@ -1,16 +1,7 @@ -/** - * Trails app environment shim. - * Parses `process.env` once at module load using Zod and exports a typed result. - * - * Adding a new variable: declare it on `trailsEnvSchema`, mark it - * `.optional()` unless every caller genuinely requires it. - */ - import { z } from 'zod'; const trailsEnvSchema = z.object({ - // Dev override: point the api client at a local server instead of the CF Worker proxy. - NEXT_PUBLIC_PACKRAT_API_ORIGIN: z.string().url().optional(), + NEXT_PUBLIC_PACKRAT_API_ORIGIN: z.string().url().default('https://api.packratai.com'), }); export type TrailsEnv = z.infer; diff --git a/apps/trails/package.json b/apps/trails/package.json index 2302376f95..bf8195400e 100644 --- a/apps/trails/package.json +++ b/apps/trails/package.json @@ -5,7 +5,6 @@ "scripts": { "build": "next build", "clean": "bunx rimraf node_modules .next out", - "deploy": "wrangler deploy", "dev": "next dev", "lint": "next lint", "start": "next start" @@ -35,7 +34,6 @@ "zod": "catalog:" }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250405.0", "@types/leaflet": "^1.9.21", "@types/node": "^25.6.0", "@types/react": "~19.1.10", @@ -43,7 +41,6 @@ "postcss": "^8.5.6", "postcss-import": "^16.1.1", "tailwindcss": "catalog:", - "typescript": "catalog:", - "wrangler": "^4.21.2" + "typescript": "catalog:" } } diff --git a/apps/trails/worker/index.ts b/apps/trails/worker/index.ts deleted file mode 100644 index 40a6aff181..0000000000 --- a/apps/trails/worker/index.ts +++ /dev/null @@ -1,94 +0,0 @@ -interface Env { - ASSETS: Fetcher; - RATE_LIMITER: { limit(opts: { key: string }): Promise<{ success: boolean }> } | undefined; - PACKRAT_API_BASE_URL: string; -} - -// Only cache responses for individual trail detail lookups (numeric OSM IDs). -// Excludes /api/trails/search and any other non-ID routes. -const TRAIL_DETAIL_RE = /^\/api\/trails\/\d+$/; -const LOCALHOST_RE = /^https?:\/\/localhost(:\d+)?$/; - -const ALLOWED_ORIGINS = new Set([ - 'https://trails.packratai.com', - 'https://staging.trails.packratai.com', -]); - -function corsHeaders(origin: string | null): Record { - const allowed = - origin !== null && (ALLOWED_ORIGINS.has(origin) || LOCALHOST_RE.test(origin)) ? origin : null; - if (!allowed) return {}; - return { - 'Access-Control-Allow-Origin': allowed, - 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - Vary: 'Origin', - }; -} - -function jsonError(status: number, body: string): Response { - return new Response(body, { status, headers: { 'Content-Type': 'application/json' } }); -} - -async function proxyToApi(request: Request, env: Env): Promise { - const url = new URL(request.url); - const origin = request.headers.get('Origin'); - - // Handle CORS preflight before rate limiting so OPTIONS never consumes quota - if (request.method === 'OPTIONS') { - return new Response(null, { status: 204, headers: corsHeaders(origin) }); - } - - // Rate limit by IP - if (env.RATE_LIMITER) { - const ip = request.headers.get('CF-Connecting-IP') ?? 'unknown'; - const { success } = await env.RATE_LIMITER.limit({ key: ip }); - if (!success) { - return new Response( - JSON.stringify({ error: 'Too many requests. Please try again in a moment.' }), - { status: 429, headers: { 'Content-Type': 'application/json', ...corsHeaders(origin) } }, - ); - } - } - - // Build upstream URL - const upstream = new URL(url.pathname + url.search, env.PACKRAT_API_BASE_URL); - - // Forward request with same headers (preserves Authorization Bearer token from client) - const proxyRequest = new Request(upstream.toString(), { - method: request.method, - headers: request.headers, - body: request.method !== 'GET' && request.method !== 'HEAD' ? request.body : null, - }); - - try { - const response = await fetch(proxyRequest); - const responseBody = await response.text(); - - const headers = new Headers(response.headers); - for (const [key, value] of Object.entries(corsHeaders(origin))) { - headers.set(key, value); - } - - // Cache trail detail responses at edge (~1 hour TTL); never cache search results - if (TRAIL_DETAIL_RE.test(url.pathname) && request.method === 'GET') { - headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=600'); - } - - return new Response(responseBody, { status: response.status, headers }); - } catch { - return jsonError(502, JSON.stringify({ error: 'API unavailable. Please try again later.' })); - } -} - -export default { - async fetch(request: Request, env: Env): Promise { - const url = new URL(request.url); - - if (url.pathname.startsWith('/api/')) { - return proxyToApi(request, env); - } - - return env.ASSETS.fetch(request); - }, -} satisfies ExportedHandler; diff --git a/apps/trails/wrangler.jsonc b/apps/trails/wrangler.jsonc deleted file mode 100644 index 01e89e501b..0000000000 --- a/apps/trails/wrangler.jsonc +++ /dev/null @@ -1,29 +0,0 @@ -{ - "$schema": "https://developers.cloudflare.com/schemas/wrangler.json", - "name": "packrat-trails", - "compatibility_date": "2025-06-01", - // Worker fetch handler: proxies /api/* to PackRat API; all other requests served from static assets - "main": "./worker/index.ts", - "assets": { - "directory": "./out", - "not_found_handling": "404-page" - }, - // Rate limiting: 60 requests per IP per 60 seconds - // Before deploying, create a namespace and set the real ID: - // wrangler rate-limit create --simple --limit 60 --period 60 - // The worker handles RATE_LIMITER being absent gracefully (no limiting in local dev). - "rate_limiting": [ - { - "binding": "RATE_LIMITER", - "namespace_id": "__REPLACE_WITH_NAMESPACE_ID__", - "simple": { - "limit": 60, - "period": 60 - } - } - ], - "vars": { - // Override in Cloudflare dashboard for production; use .dev.vars locally - "PACKRAT_API_BASE_URL": "https://api.packratai.com" - } -} diff --git a/bun.lock b/bun.lock index 69b26deb20..ee98c13547 100644 --- a/bun.lock +++ b/bun.lock @@ -367,7 +367,6 @@ "zod": "catalog:", }, "devDependencies": { - "@cloudflare/workers-types": "^4.20250405.0", "@types/leaflet": "^1.9.21", "@types/node": "^25.6.0", "@types/react": "~19.1.10", @@ -376,7 +375,6 @@ "postcss-import": "^16.1.1", "tailwindcss": "catalog:", "typescript": "catalog:", - "wrangler": "^4.21.2", }, }, "packages/analytics": { From 5d75348ce3914f40f847261be3b8cb0f04f7915b Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Thu, 7 May 2026 21:39:22 -0600 Subject: [PATCH 21/21] =?UTF-8?q?chore(trails):=20align=20API=20URL=20env?= =?UTF-8?q?=20var=20with=20Expo=20=E2=80=94=20NEXT=5FPUBLIC=5FAPI=5FURL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- apps/trails/lib/apiClient.ts | 2 +- apps/trails/lib/env.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/trails/lib/apiClient.ts b/apps/trails/lib/apiClient.ts index f01fec991d..f5e1dd6dab 100644 --- a/apps/trails/lib/apiClient.ts +++ b/apps/trails/lib/apiClient.ts @@ -11,7 +11,7 @@ import { import { trailsEnv } from 'trails-app/lib/env'; export const apiClient = createApiClient({ - baseUrl: trailsEnv.NEXT_PUBLIC_PACKRAT_API_ORIGIN, + baseUrl: trailsEnv.NEXT_PUBLIC_API_URL, auth: { getAccessToken, getRefreshToken, diff --git a/apps/trails/lib/env.ts b/apps/trails/lib/env.ts index 582d555d45..bdfed7f7d0 100644 --- a/apps/trails/lib/env.ts +++ b/apps/trails/lib/env.ts @@ -1,11 +1,11 @@ import { z } from 'zod'; const trailsEnvSchema = z.object({ - NEXT_PUBLIC_PACKRAT_API_ORIGIN: z.string().url().default('https://api.packratai.com'), + NEXT_PUBLIC_API_URL: z.string().url().default('https://api.packratai.com'), }); export type TrailsEnv = z.infer; export const trailsEnv = trailsEnvSchema.parse({ - NEXT_PUBLIC_PACKRAT_API_ORIGIN: process.env.NEXT_PUBLIC_PACKRAT_API_ORIGIN, + NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL, });