diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6f811354..7fdc7dff 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -110,11 +110,8 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build web application - run: pnpm build:web - - name: Verify unsigned Android debug application - run: pnpm --filter @t4-code/mobile check:android:debug + run: pnpm --filter @t4-code/companion check:android:native verify: name: verify diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c9027c6c..ddefe73a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -208,11 +208,8 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile - - name: Build web application for Android - run: pnpm build:web - - name: Verify unsigned Android application - run: pnpm --filter @t4-code/mobile check:android:debug + run: pnpm --filter @t4-code/companion check:android:native - name: Restore and validate Android release signing key shell: bash @@ -249,14 +246,14 @@ jobs: T4_ANDROID_KEY_ALIAS: ${{ secrets.T4_ANDROID_KEY_ALIAS }} T4_ANDROID_KEY_PASSWORD: ${{ secrets.T4_ANDROID_KEY_PASSWORD }} run: | - pnpm --filter @t4-code/mobile build:android:release + pnpm --filter @t4-code/companion build:android:release - name: Inspect signed Android APK shell: bash run: | set -euo pipefail - apk="apps/mobile/android/app/build/outputs/apk/release/app-release.apk" - metadata="apps/mobile/android/app/build/outputs/apk/release/output-metadata.json" + apk="apps/companion/android/app/build/outputs/apk/release/app-release.apk" + metadata="apps/companion/android/app/build/outputs/apk/release/output-metadata.json" build_tools="${ANDROID_SDK_ROOT:-$ANDROID_HOME}/build-tools/36.0.0" node scripts/inspect-android-release.mjs \ --apk "$apk" \ @@ -271,7 +268,7 @@ jobs: run: | mkdir -p artifacts cp \ - apps/mobile/android/app/build/outputs/apk/release/app-release.apk \ + apps/companion/android/app/build/outputs/apk/release/app-release.apk \ "artifacts/T4-Code-${VERSION}-android.apk" (cd artifacts && sha256sum T4-Code-* > SHA256SUMS-android.txt) diff --git a/apps/companion/.gitignore b/apps/companion/.gitignore new file mode 100644 index 00000000..4b00baf3 --- /dev/null +++ b/apps/companion/.gitignore @@ -0,0 +1,43 @@ +# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files + +# dependencies +node_modules/ + +# Expo +.expo/ +dist/ +web-build/ +expo-env.d.ts + +# Native +.kotlin/ +*.orig.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision + +# Metro +.metro-health-check* + +# debug +npm-debug.* +yarn-debug.* +yarn-error.* + +# macOS +.DS_Store +*.pem + +# local env files +.env*.local + +# typescript +*.tsbuildinfo + +example + +# generated native folders +/ios +/android diff --git a/apps/companion/.vscode/extensions.json b/apps/companion/.vscode/extensions.json new file mode 100644 index 00000000..b7ed8377 --- /dev/null +++ b/apps/companion/.vscode/extensions.json @@ -0,0 +1 @@ +{ "recommendations": ["expo.vscode-expo-tools"] } diff --git a/apps/companion/.vscode/settings.json b/apps/companion/.vscode/settings.json new file mode 100644 index 00000000..e2798e42 --- /dev/null +++ b/apps/companion/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "editor.codeActionsOnSave": { + "source.fixAll": "explicit", + "source.organizeImports": "explicit", + "source.sortMembers": "explicit" + } +} diff --git a/apps/companion/AGENTS.md b/apps/companion/AGENTS.md new file mode 100644 index 00000000..3c5047c2 --- /dev/null +++ b/apps/companion/AGENTS.md @@ -0,0 +1,5 @@ +# T4 Companion build notes + +- Keep the checkout path free of spaces for native iOS work. Expo SDK 57's generated `EXConstants` build phase truncates a spaced workspace path and fails with `No such file or directory`. +- Run authoritative checks with the repository's Node 24 toolchain. +- The `ios/` and `android/` directories are generated by Expo prebuild and stay ignored. Change `app.json` or config plugins instead of hand-editing generated native files. diff --git a/apps/companion/LICENSE b/apps/companion/LICENSE new file mode 100644 index 00000000..30b20e3b --- /dev/null +++ b/apps/companion/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-present 650 Industries, Inc. (aka Expo) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/apps/companion/README.md b/apps/companion/README.md new file mode 100644 index 00000000..375fdf7c --- /dev/null +++ b/apps/companion/README.md @@ -0,0 +1,102 @@ +# T4 Companion + +T4 Companion is the iPhone and Android front end for a T4 Code host running on +your Mac. It remembers one stable Tailscale address, reconnects when the app +returns to the foreground, and shows sessions, transcripts, questions, and +approval requests without exposing the host to the public internet. + +## One-time phone setup + +1. Install Tailscale on the Mac and phone, then sign into the same Tailnet on + both devices. A Tailnet is the private network Tailscale creates for you. +2. On the Mac, open T4 Code's **Phone Setup** card and select + **Companion app**. +3. Scan the QR code with the phone and open the `t4companion://` link. +4. Enter the pairing code shown by T4 Code if the host asks for one. + +The app saves the Mac's `.ts.net` address in the phone's protected storage. +That address stays the same unless the Mac is renamed in Tailscale, so normal +use does not require scanning again. + +## Development + +Use Node 24 and install the monorepo dependencies from the repository root: + +```bash +mise exec node@24.17.0 -- pnpm install +``` + +Run the readiness check before generating native projects. It explains any +missing prerequisite and exits successfully only when the selected platform is +ready: + +```bash +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion doctor:ios +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion doctor:android +``` + +This checkout path must not contain spaces when building the native iOS app. +Expo SDK 57 otherwise generates a broken native file path. + +For a physical iPhone, one command now discovers the connected phone, Apple +development team, and stable Tailnet address. It builds a signed Release app, +installs it, opens it, and passes the address into the app: + +```bash +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion ios:device +``` + +The iPhone must be unlocked for the final automatic launch. The command waits +up to one minute if it locks during the build. To retry only the deep link +without rebuilding or reinstalling, run: + +```bash +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion ios:device --launch-only +``` + +Use `ios:device --help` for explicit device, Apple team, Tailnet URL, and +install-without-launch options. + +For a simulator, run the iOS development build: + +```bash +cd apps/companion +mise exec node@24.17.0 -- pnpm exec expo run:ios +``` + +The first physical-iPhone build requires an Apple Development certificate and +a provisioning profile for `com.lycaonsolutions.t4code`. Xcode can create both +after the project owner signs in under **Xcode Settings > Accounts**. This is a +one-time Apple Developer account change; later local installs reuse it. + +For Android, the matching one-command workflow discovers a connected phone or +starts the `T4_Pixel_API_36` emulator, builds a Release APK for that device's +processor, installs it, opens it, and passes the stable Tailnet address: + +```bash +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion android:device +``` + +To reopen the installed app without rebuilding, run: + +```bash +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion android:device --launch-only +``` + +Use `android:device --help` for an explicit device, virtual-device name, +Tailnet URL, build reuse, and install-without-launch options. The Android phone +or emulator must be able to reach the Mac's Tailnet address. Run +`doctor:android` first if Java 17, the Android SDK, platform tools, or a target +device are missing. + +Run focused checks: + +```bash +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion typecheck +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion test +mise exec node@24.17.0 -- pnpm --filter @t4-code/companion lint +``` + +The `ios/` and `android/` directories are generated by Expo and are ignored. +Make permanent native configuration changes in `app.json` or an Expo config +plugin instead of editing generated files. diff --git a/apps/companion/app.json b/apps/companion/app.json new file mode 100644 index 00000000..f724169d --- /dev/null +++ b/apps/companion/app.json @@ -0,0 +1,62 @@ +{ + "expo": { + "name": "T4 Companion", + "slug": "t4-companion", + "version": "0.1.29", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "t4companion", + "userInterfaceStyle": "dark", + "backgroundColor": "#090b0f", + "ios": { + "icon": "./assets/images/icon.png", + "bundleIdentifier": "com.lycaonsolutions.t4code", + "supportsTablet": false, + "buildNumber": "10029" + }, + "android": { + "package": "com.lycaonsolutions.t4code", + "adaptiveIcon": { + "backgroundColor": "#090b0f", + "foregroundImage": "./assets/images/android-icon-foreground.png", + "monochromeImage": "./assets/images/android-icon-monochrome.png" + }, + "predictiveBackGestureEnabled": true, + "versionCode": 10029 + }, + "web": { + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router", + [ + "expo-splash-screen", + { + "backgroundColor": "#090b0f", + "image": "./assets/images/splash-icon.png", + "imageWidth": 76 + } + ], + [ + "expo-notifications", + { + "defaultChannel": "attention" + } + ], + "expo-secure-store", + [ + "expo-speech-recognition", + { + "microphonePermission": "Allow T4 Companion to transcribe voice input.", + "speechRecognitionPermission": "Allow T4 Companion to turn speech into prompt text." + } + ], + "./plugins/with-android-release-signing.cjs" + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + } + } +} diff --git a/apps/companion/assets/images/android-icon-foreground.png b/apps/companion/assets/images/android-icon-foreground.png new file mode 100644 index 00000000..5ee02358 Binary files /dev/null and b/apps/companion/assets/images/android-icon-foreground.png differ diff --git a/apps/companion/assets/images/android-icon-monochrome.png b/apps/companion/assets/images/android-icon-monochrome.png new file mode 100644 index 00000000..5ee02358 Binary files /dev/null and b/apps/companion/assets/images/android-icon-monochrome.png differ diff --git a/apps/companion/assets/images/favicon.png b/apps/companion/assets/images/favicon.png new file mode 100644 index 00000000..408bd746 Binary files /dev/null and b/apps/companion/assets/images/favicon.png differ diff --git a/apps/companion/assets/images/icon.png b/apps/companion/assets/images/icon.png new file mode 100644 index 00000000..f1f6feab Binary files /dev/null and b/apps/companion/assets/images/icon.png differ diff --git a/apps/companion/assets/images/splash-icon.png b/apps/companion/assets/images/splash-icon.png new file mode 100644 index 00000000..f1f6feab Binary files /dev/null and b/apps/companion/assets/images/splash-icon.png differ diff --git a/apps/companion/eas.json b/apps/companion/eas.json new file mode 100644 index 00000000..9cb27835 --- /dev/null +++ b/apps/companion/eas.json @@ -0,0 +1,24 @@ +{ + "cli": { + "version": ">= 19.1.0", + "appVersionSource": "remote" + }, + "build": { + "development-simulator": { + "developmentClient": true, + "distribution": "internal", + "ios": { + "simulator": true + } + }, + "preview": { + "distribution": "internal" + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": {} + } +} diff --git a/apps/companion/eslint.config.js b/apps/companion/eslint.config.js new file mode 100644 index 00000000..289c19ab --- /dev/null +++ b/apps/companion/eslint.config.js @@ -0,0 +1,9 @@ +const expoConfig = require("eslint-config-expo/flat"); +const { defineConfig } = require("eslint/config"); + +module.exports = defineConfig([ + expoConfig, + { + ignores: ["android/**", "ios/**"], + }, +]); diff --git a/apps/companion/metro.config.js b/apps/companion/metro.config.js new file mode 100644 index 00000000..95748943 --- /dev/null +++ b/apps/companion/metro.config.js @@ -0,0 +1,20 @@ +const { getDefaultConfig } = require("expo/metro-config"); + +const config = getDefaultConfig(__dirname); + +// T4's shared protocol packages publish TypeScript source while using the +// standards-friendly `.js` import spelling expected after compilation. Metro +// does not map those relative imports back to `.ts` by default, so retry the +// same request without the output extension when the importer is TypeScript. +config.resolver.resolveRequest = (context, moduleName, platform) => { + const isTypeScriptImporter = /\.[cm]?tsx?$/.test(context.originModulePath); + const isRelativeJavaScriptImport = moduleName.startsWith(".") && moduleName.endsWith(".js"); + + if (isTypeScriptImporter && isRelativeJavaScriptImport) { + return context.resolveRequest(context, moduleName.slice(0, -3), platform); + } + + return context.resolveRequest(context, moduleName, platform); +}; + +module.exports = config; diff --git a/apps/companion/package.json b/apps/companion/package.json new file mode 100644 index 00000000..dc285c30 --- /dev/null +++ b/apps/companion/package.json @@ -0,0 +1,50 @@ +{ + "name": "@t4-code/companion", + "main": "expo-router/entry", + "version": "0.1.29", + "dependencies": { + "@t4-code/client": "workspace:*", + "@t4-code/protocol": "workspace:*", + "expo": "~57.0.7", + "expo-constants": "~57.0.6", + "expo-linking": "~57.0.3", + "expo-notifications": "^57.0.6", + "expo-router": "~57.0.7", + "expo-secure-store": "~57.0.1", + "expo-speech": "^57.0.1", + "expo-speech-recognition": "56.0.1", + "expo-splash-screen": "~57.0.4", + "expo-status-bar": "~57.0.1", + "expo-system-ui": "~57.0.1", + "react": "19.2.3", + "react-dom": "19.2.3", + "react-native": "0.86.0", + "react-native-safe-area-context": "~5.7.0", + "react-native-screens": "4.25.2", + "react-native-web": "^0.21.2" + }, + "devDependencies": { + "@types/react": "~19.2.2", + "eslint": "^9.39.5", + "eslint-config-expo": "~57.0.0", + "typescript": "~6.0.3", + "vite-plus": "catalog:" + }, + "scripts": { + "doctor": "node scripts/doctor.mjs", + "doctor:ios": "node scripts/doctor.mjs --platform ios", + "doctor:android": "node scripts/doctor.mjs --platform android", + "ios:device": "node scripts/ios-device.mjs", + "android:device": "node scripts/android-device.mjs", + "start": "expo start", + "android": "expo run:android", + "ios": "expo run:ios", + "web": "expo start --web", + "lint": "expo lint", + "typecheck": "tsc --noEmit", + "test": "vp test run && node --test scripts/doctor-node-tests.mjs scripts/android-release-node-tests.mjs", + "check:android:native": "node scripts/android-build.mjs check", + "build:android:release": "node scripts/android-build.mjs release" + }, + "private": true +} diff --git a/apps/companion/plugins/with-android-release-signing.cjs b/apps/companion/plugins/with-android-release-signing.cjs new file mode 100644 index 00000000..6ba7279b --- /dev/null +++ b/apps/companion/plugins/with-android-release-signing.cjs @@ -0,0 +1,55 @@ +const { withAppBuildGradle } = require("expo/config-plugins"); + +const MARKER = "// T4 managed release signing"; + +function applyAndroidReleaseSigning(contents) { + if (contents.includes(MARKER)) return contents; + + const propertyAnchor = + "def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()"; + const signingAnchor = " signingConfigs {\n debug {"; + const buildTypesAnchor = " buildTypes {"; + const releaseBlockAnchor = "\n release {"; + const releaseBlockEndAnchor = "\n }\n }"; + const releaseSigningAnchor = " signingConfig signingConfigs.debug"; + + if (!contents.includes(propertyAnchor)) { + throw new Error("Expo Android template no longer exposes the expected release property anchor."); + } + if (!contents.includes(signingAnchor)) { + throw new Error("Expo Android template no longer exposes the expected signingConfigs anchor."); + } + const buildTypesStart = contents.indexOf(buildTypesAnchor); + const releaseBlockStart = contents.indexOf(releaseBlockAnchor, buildTypesStart); + const releaseBlockEnd = contents.indexOf(releaseBlockEndAnchor, releaseBlockStart); + const releaseSigningStart = contents.indexOf(releaseSigningAnchor, releaseBlockStart); + if ( + buildTypesStart < 0 || + releaseBlockStart < 0 || + releaseBlockEnd < 0 || + releaseSigningStart < 0 || + releaseSigningStart > releaseBlockEnd + ) { + throw new Error("Expo Android template no longer exposes the expected release signing anchor."); + } + + const properties = `${propertyAnchor}\n\n${MARKER}\ndef t4ReleaseKeystorePath = System.getenv('T4_ANDROID_KEYSTORE_PATH')\ndef t4ReleaseKeystorePassword = System.getenv('T4_ANDROID_KEYSTORE_PASSWORD')\ndef t4ReleaseKeyAlias = System.getenv('T4_ANDROID_KEY_ALIAS')\ndef t4ReleaseKeyPassword = System.getenv('T4_ANDROID_KEY_PASSWORD')\ndef t4HasReleaseSigning = [\n t4ReleaseKeystorePath,\n t4ReleaseKeystorePassword,\n t4ReleaseKeyAlias,\n t4ReleaseKeyPassword,\n].every { value -> value != null && !value.isBlank() }`; + + const signing = ` signingConfigs {\n if (t4HasReleaseSigning) {\n release {\n storeFile file(t4ReleaseKeystorePath)\n storePassword t4ReleaseKeystorePassword\n keyAlias t4ReleaseKeyAlias\n keyPassword t4ReleaseKeyPassword\n }\n }\n debug {`; + + const releaseConfigured = `${contents.slice(0, releaseSigningStart)} signingConfig t4HasReleaseSigning ? signingConfigs.release : signingConfigs.debug${contents.slice(releaseSigningStart + releaseSigningAnchor.length)}`; + return releaseConfigured.replace(propertyAnchor, properties).replace(signingAnchor, signing); +} + +function withAndroidReleaseSigning(config) { + return withAppBuildGradle(config, (modConfig) => { + if (modConfig.modResults.language !== "groovy") { + throw new Error("T4 Android release signing requires a Groovy app/build.gradle file."); + } + modConfig.modResults.contents = applyAndroidReleaseSigning(modConfig.modResults.contents); + return modConfig; + }); +} + +module.exports = withAndroidReleaseSigning; +module.exports.applyAndroidReleaseSigning = applyAndroidReleaseSigning; diff --git a/apps/companion/scripts/android-build.mjs b/apps/companion/scripts/android-build.mjs new file mode 100755 index 00000000..42f3cd88 --- /dev/null +++ b/apps/companion/scripts/android-build.mjs @@ -0,0 +1,108 @@ +#!/usr/bin/env node + +import { existsSync } from "node:fs"; +import { join, resolve } from "node:path"; +import { spawnSync } from "node:child_process"; +import process from "node:process"; +import { pathToFileURL } from "node:url"; + +const packageDirectory = resolve(import.meta.dirname, ".."); +const androidDirectory = join(packageDirectory, "android"); +const releaseApk = join(androidDirectory, "app", "build", "outputs", "apk", "release", "app-release.apk"); +const signingVariables = Object.freeze([ + "T4_ANDROID_KEYSTORE_PATH", + "T4_ANDROID_KEYSTORE_PASSWORD", + "T4_ANDROID_KEY_ALIAS", + "T4_ANDROID_KEY_PASSWORD", +]); + +function output(result) { + return `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(); +} + +function run(command, args, options = {}) { + const result = spawnSync(command, args, { + cwd: packageDirectory, + encoding: "utf8", + stdio: "inherit", + timeout: 20 * 60_000, + ...options, + }); + if (result.status !== 0) throw new Error(output(result) || `${command} exited with status ${result.status}.`); +} + +export function parseAndroidBuildArguments(argumentsList) { + if (argumentsList.length !== 1 || !["check", "release", "--help"].includes(argumentsList[0])) { + throw new Error("usage: android-build.mjs "); + } + return argumentsList[0]; +} + +export function missingSigningVariables(environment = process.env) { + return signingVariables.filter((name) => typeof environment[name] !== "string" || environment[name].length === 0); +} + +export function androidGradleArguments(mode) { + return mode === "check" + ? [ + "app:testDebugUnitTest", + "app:assembleDebug", + "app:lintDebug", + // Worklets and Reanimated 4 crash under AGP 9 lint analysis; app lint still runs. + "-x", + "react-native-worklets:lintAnalyzeDebug", + "-x", + "react-native-reanimated:lintAnalyzeDebug", + ] + : ["app:assembleRelease"]; +} + +function printUsage() { + console.log(`Generate and compile the native Expo Android project. + +Usage: + node scripts/android-build.mjs check + node scripts/android-build.mjs release + +The release command requires ${signingVariables.join(", ")}.`); +} + +function generateNativeProject() { + run("pnpm", ["exec", "expo", "prebuild", "--clean", "--platform", "android", "--no-install"], { + timeout: 5 * 60_000, + }); + const wrapper = join(androidDirectory, "gradlew"); + if (!existsSync(wrapper)) throw new Error("Expo prebuild completed without generating android/gradlew."); + return wrapper; +} + +function main(argumentsList) { + const mode = parseAndroidBuildArguments(argumentsList); + if (mode === "--help") { + printUsage(); + return; + } + if (mode === "release") { + const missing = missingSigningVariables(); + if (missing.length > 0) throw new Error(`Android release signing is incomplete: ${missing.join(", ")}.`); + if (!existsSync(process.env.T4_ANDROID_KEYSTORE_PATH)) { + throw new Error("T4_ANDROID_KEYSTORE_PATH does not point to a file."); + } + } + + const wrapper = generateNativeProject(); + run(wrapper, androidGradleArguments(mode), { cwd: androidDirectory }); + + if (mode === "release" && !existsSync(releaseApk)) { + throw new Error("The Android build completed without producing app-release.apk."); + } +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + try { + main(process.argv.slice(2)); + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 1; + } +} diff --git a/apps/companion/scripts/android-device.mjs b/apps/companion/scripts/android-device.mjs new file mode 100644 index 00000000..5139522e --- /dev/null +++ b/apps/companion/scripts/android-device.mjs @@ -0,0 +1,260 @@ +#!/usr/bin/env node + +import { existsSync } from "node:fs"; +import { homedir } from "node:os"; +import { delimiter, join, resolve } from "node:path"; +import { spawn, spawnSync } from "node:child_process"; +import process from "node:process"; +import { pathToFileURL } from "node:url"; + +import { companionDeepLink } from "./ios-device.mjs"; + +const packageDirectory = resolve(import.meta.dirname, ".."); +const repositoryRoot = resolve(packageDirectory, "../.."); +const applicationId = "com.lycaonsolutions.t4code"; +const defaultAvd = "T4_Pixel_API_36"; +const releaseApk = join(packageDirectory, "android", "app", "build", "outputs", "apk", "release", "app-release.apk"); + +function commandOutput(result) { + return `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(); +} + +function run(command, args = [], options = {}) { + return spawnSync(command, args, { encoding: "utf8", timeout: 30_000, ...options }); +} + +function runOrThrow(command, args = [], options = {}) { + const result = run(command, args, options); + if (result.status !== 0) throw new Error(commandOutput(result) || `${command} exited with status ${result.status}.`); + return commandOutput(result); +} + +function argumentValue(argumentsList, name) { + const index = argumentsList.indexOf(name); + if (index === -1) return ""; + const value = argumentsList[index + 1]; + if (!value || value.startsWith("--")) throw new Error(`${name} needs a value.`); + return value; +} + +export function parseAndroidArguments(argumentsList) { + const valued = new Set(["--device", "--avd", "--url"]); + const switches = new Set(["--reuse-build", "--launch-only", "--no-launch", "--help"]); + for (let index = 0; index < argumentsList.length; index += 1) { + const argument = argumentsList[index]; + if (!valued.has(argument) && !switches.has(argument)) throw new Error(`Unknown option: ${argument}`); + if (valued.has(argument)) index += 1; + } + const parsed = { + device: argumentValue(argumentsList, "--device"), + avd: argumentValue(argumentsList, "--avd") || defaultAvd, + url: argumentValue(argumentsList, "--url"), + reuseBuild: argumentsList.includes("--reuse-build"), + launchOnly: argumentsList.includes("--launch-only"), + noLaunch: argumentsList.includes("--no-launch"), + help: argumentsList.includes("--help"), + }; + if (parsed.launchOnly && parsed.noLaunch) throw new Error("--launch-only and --no-launch cannot be used together."); + return parsed; +} + +export function parseAdbDevices(output) { + return output + .split("\n") + .slice(1) + .map((line) => line.trim()) + .filter((line) => /\sdevice(?:\s|$)/u.test(line)) + .map((line) => { + const [serial] = line.split(/\s+/u); + const model = /(?:^|\s)model:([^\s]+)/u.exec(line)?.[1]?.replaceAll("_", " ") ?? serial; + return { serial, model }; + }); +} + +export function resolveAndroidSdkRoot(environment = process.env, userHome = homedir(), pathExists = existsSync) { + const candidates = [ + environment.ANDROID_HOME, + environment.ANDROID_SDK_ROOT, + join(userHome, "Library", "Android", "sdk"), + join(userHome, "Android", "Sdk"), + "/opt/homebrew/share/android-commandlinetools", + "/usr/local/share/android-commandlinetools", + ]; + return candidates.find((candidate) => typeof candidate === "string" && pathExists(join(candidate, "platforms"))) ?? ""; +} + +export function resolveAndroidJavaHome(environment = process.env, pathExists = existsSync) { + const candidates = [ + environment.JAVA_HOME, + "/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home", + "/usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home", + "/Applications/Android Studio.app/Contents/jbr/Contents/Home", + ]; + return candidates.find((candidate) => { + if (typeof candidate !== "string" || !pathExists(join(candidate, "bin", "java"))) return false; + const version = run(join(candidate, "bin", "java"), ["-version"]); + const major = /version "(\d+)/u.exec(commandOutput(version))?.[1] ?? ""; + return major === "17" || major === "21"; + }) ?? ""; +} + +function printUsage() { + console.log(`Build, install, and open T4 Companion on Android. + +Usage: + pnpm --filter @t4-code/companion android:device + +Options: + --device SERIAL Use a connected Android device or emulator + --avd NAME Boot this virtual device when none is running (default: ${defaultAvd}) + --url URL Override the stable Tailnet address inferred from Tailscale + --reuse-build Reinstall the last Release APK without rebuilding it + --launch-only Open the installed app without rebuilding or reinstalling + --no-launch Install the app but do not open it + --help Show this help`); +} + +function resolveTailnetAddress(override) { + if (override) return override; + const result = run("tailscale", ["status", "--json"]); + if (result.status !== 0) throw new Error("Connect Tailscale on this Mac, or pass --url with the stable T4 host address."); + const parsed = JSON.parse(result.stdout); + const dnsName = String(parsed?.Self?.DNSName ?? "").replace(/\.$/u, ""); + if (!dnsName.endsWith(".ts.net")) throw new Error("Tailscale did not return a stable .ts.net name for this Mac."); + return `https://${dnsName}:8445`; +} + +function androidEnvironment(sdkRoot, javaHome) { + return { + ...process.env, + ANDROID_HOME: sdkRoot, + ANDROID_SDK_ROOT: sdkRoot, + JAVA_HOME: javaHome, + PATH: [join(javaHome, "bin"), join(sdkRoot, "platform-tools"), process.env.PATH ?? ""].join(delimiter), + }; +} + +function adbPath(sdkRoot) { + const bundled = join(sdkRoot, "platform-tools", "adb"); + return existsSync(bundled) ? bundled : "adb"; +} + +function connectedDevices(adb, environment) { + const output = runOrThrow(adb, ["devices", "-l"], { env: environment }); + return parseAdbDevices(output); +} + +function selectDevice(devices, requested) { + if (requested) { + const match = devices.find((device) => device.serial === requested || device.model === requested); + if (!match) throw new Error(`Android device ${requested} is not connected.`); + return match; + } + return devices[0]; +} + +function delay(milliseconds) { + return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds)); +} + +async function ensureDevice(adb, sdkRoot, environment, requested, avdName) { + let devices = connectedDevices(adb, environment); + if (devices.length > 0) return selectDevice(devices, requested); + if (requested) throw new Error(`Android device ${requested} is not connected.`); + + const emulator = join(sdkRoot, "emulator", "emulator"); + if (!existsSync(emulator)) throw new Error("No Android device is connected and the SDK emulator is not installed."); + const avds = runOrThrow(emulator, ["-list-avds"], { env: environment }).split("\n").filter(Boolean); + if (!avds.includes(avdName)) { + throw new Error(`No Android device is connected and virtual device ${avdName} does not exist. Run doctor:android for setup instructions.`); + } + console.log(`Starting Android virtual device ${avdName}…`); + const child = spawn(emulator, [`@${avdName}`, "-no-boot-anim", "-no-audio", "-no-metrics", "-no-snapshot-save", "-gpu", "host"], { + detached: true, + env: environment, + stdio: "ignore", + }); + child.unref(); + + const deadline = Date.now() + 120_000; + while (Date.now() < deadline) { + await delay(2_000); + devices = connectedDevices(adb, environment); + const device = devices[0]; + if (!device) continue; + const booted = run(adb, ["-s", device.serial, "shell", "getprop", "sys.boot_completed"], { env: environment }); + if (booted.stdout?.trim() === "1") return device; + } + throw new Error(`Virtual device ${avdName} did not finish starting within two minutes.`); +} + +function ensureNativeProject(environment) { + if (existsSync(join(packageDirectory, "android", "gradlew"))) return; + console.log("Generating the native Android project…"); + runOrThrow("pnpm", ["--filter", "@t4-code/companion", "exec", "expo", "prebuild", "--platform", "android", "--no-install"], { + cwd: repositoryRoot, + env: environment, + stdio: "inherit", + timeout: 5 * 60_000, + }); +} + +function buildRelease(adb, device, environment) { + ensureNativeProject(environment); + const abi = runOrThrow(adb, ["-s", device.serial, "shell", "getprop", "ro.product.cpu.abi"], { env: environment }); + console.log(`Building the Android Release app for ${abi}…`); + runOrThrow(join(packageDirectory, "android", "gradlew"), ["app:assembleRelease", `-PreactNativeArchitectures=${abi}`], { + cwd: join(packageDirectory, "android"), + env: environment, + stdio: "inherit", + timeout: 20 * 60_000, + }); + if (!existsSync(releaseApk)) throw new Error("The Android build completed without producing the expected Release APK."); +} + +async function main(argumentsList) { + const options = parseAndroidArguments(argumentsList); + if (options.help) { + printUsage(); + return; + } + const sdkRoot = resolveAndroidSdkRoot(); + if (!sdkRoot) throw new Error("Android SDK not found. Run doctor:android for the exact setup steps."); + const javaHome = resolveAndroidJavaHome(); + if (!javaHome) throw new Error("Java 17 or 21 not found. Install openjdk@17, then rerun this command."); + const environment = androidEnvironment(sdkRoot, javaHome); + const adb = adbPath(sdkRoot); + const device = await ensureDevice(adb, sdkRoot, environment, options.device, options.avd); + const address = resolveTailnetAddress(options.url); + const deepLink = companionDeepLink(address); + + console.log(`Android: ${device.model} (${device.serial})`); + console.log(`T4 host: ${address}`); + + if (!options.launchOnly) { + if (!options.reuseBuild) buildRelease(adb, device, environment); + if (!existsSync(releaseApk)) throw new Error("No reusable Release APK exists. Run without --reuse-build first."); + console.log("Installing the Release app…"); + runOrThrow(adb, ["-s", device.serial, "install", "-r", releaseApk], { env: environment, timeout: 3 * 60_000 }); + } + + if (options.noLaunch) { + console.log(`Installed. Open T4 Companion on Android; its host is ${address}`); + return; + } + console.log("Opening T4 Companion with the saved Tailnet address…"); + run(adb, ["-s", device.serial, "shell", "am", "force-stop", applicationId], { env: environment }); + runOrThrow(adb, ["-s", device.serial, "shell", "am", "start", "-a", "android.intent.action.VIEW", "-d", deepLink, applicationId], { env: environment }); + await delay(2_000); + const pid = runOrThrow(adb, ["-s", device.serial, "shell", "pidof", applicationId], { env: environment }); + console.log(`T4 Companion is installed, open, and configured (process ${pid}).`); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + try { + await main(process.argv.slice(2)); + } catch (error) { + console.error(`\nT4 Android setup stopped: ${error instanceof Error ? error.message : String(error)}`); + process.exitCode = 1; + } +} diff --git a/apps/companion/scripts/android-release-node-tests.mjs b/apps/companion/scripts/android-release-node-tests.mjs new file mode 100644 index 00000000..48a8e09f --- /dev/null +++ b/apps/companion/scripts/android-release-node-tests.mjs @@ -0,0 +1,81 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { createRequire } from "node:module"; + +import { + androidGradleArguments, + missingSigningVariables, + parseAndroidBuildArguments, +} from "./android-build.mjs"; + +const require = createRequire(import.meta.url); +const { applyAndroidReleaseSigning } = require("../plugins/with-android-release-signing.cjs"); + +const expoTemplate = `def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean() + +android { + signingConfigs { + debug { + storeFile file('debug.keystore') + } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + signingConfig signingConfigs.debug + } + } +} +`; + +test("Android release build arguments are closed over supported modes", () => { + assert.equal(parseAndroidBuildArguments(["check"]), "check"); + assert.equal(parseAndroidBuildArguments(["release"]), "release"); + assert.throws(() => parseAndroidBuildArguments([]), /usage/u); + assert.throws(() => parseAndroidBuildArguments(["release", "extra"]), /usage/u); +}); + +test("Android checks skip the incompatible worklets dependency lint task", () => { + assert.deepEqual(androidGradleArguments("check"), [ + "app:testDebugUnitTest", + "app:assembleDebug", + "app:lintDebug", + "-x", + "react-native-worklets:lintAnalyzeDebug", + "-x", + "react-native-reanimated:lintAnalyzeDebug", + ]); + assert.deepEqual(androidGradleArguments("release"), ["app:assembleRelease"]); +}); + +test("release signing requires every credential without exposing values", () => { + const complete = { + T4_ANDROID_KEYSTORE_PATH: "/tmp/release.jks", + T4_ANDROID_KEYSTORE_PASSWORD: "store-secret", + T4_ANDROID_KEY_ALIAS: "release", + T4_ANDROID_KEY_PASSWORD: "key-secret", + }; + assert.deepEqual(missingSigningVariables(complete), []); + assert.deepEqual(missingSigningVariables({ ...complete, T4_ANDROID_KEY_PASSWORD: "" }), [ + "T4_ANDROID_KEY_PASSWORD", + ]); +}); + +test("config plugin replaces debug release signing idempotently", () => { + const configured = applyAndroidReleaseSigning(expoTemplate); + assert.match(configured, /T4 managed release signing/u); + assert.match(configured, /System\.getenv\('T4_ANDROID_KEYSTORE_PATH'\)/u); + assert.ok(configured.includes("debug {\n signingConfig signingConfigs.debug")); + assert.ok( + configured.includes( + "release {\n signingConfig t4HasReleaseSigning ? signingConfigs.release : signingConfigs.debug", + ), + ); + assert.equal(applyAndroidReleaseSigning(configured), configured); +}); + +test("config plugin fails closed when the Expo template changes", () => { + assert.throws(() => applyAndroidReleaseSigning("android {}"), /expected release property anchor/u); +}); diff --git a/apps/companion/scripts/doctor-node-tests.mjs b/apps/companion/scripts/doctor-node-tests.mjs new file mode 100644 index 00000000..75b89a05 --- /dev/null +++ b/apps/companion/scripts/doctor-node-tests.mjs @@ -0,0 +1,156 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { evaluateDoctor, hasPairedIphone } from "./doctor.mjs"; +import { + appleTeamIdFromCertificateSubject, + companionDeepLink, + launchFailureIsLocked, + pairedPhysicalIphones, + parseArguments, +} from "./ios-device.mjs"; +import { + parseAdbDevices, + parseAndroidArguments, + resolveAndroidSdkRoot, +} from "./android-device.mjs"; + +const ready = Object.freeze({ + nodeMajor: 24, + nodeVersion: "24.17.0", + checkoutHasSpaces: false, + tailnetName: "mac.example.ts.net", + xcodeAvailable: true, + xcodeVersion: "Xcode 26.5", + iphoneAvailable: true, + appleDevelopmentIdentity: true, + androidSdkAvailable: true, + androidSdkRoot: "/sdk", + adbAvailable: true, + androidJavaVersion: "17", + androidTargetAvailable: true, +}); + +test("all-platform readiness passes when every prerequisite is present", () => { + assert.equal(evaluateDoctor(ready).every((check) => check.ok), true); +}); + +test("iOS mode reports signing and path problems without requiring Android", () => { + const checks = evaluateDoctor({ + ...ready, + checkoutHasSpaces: true, + appleDevelopmentIdentity: false, + androidSdkAvailable: false, + adbAvailable: false, + }, "ios"); + assert.deepEqual( + checks.filter((check) => !check.ok).map((check) => check.name), + ["Checkout path", "Apple development signing"], + ); + assert.equal(checks.some((check) => check.name === "Android SDK"), false); +}); + +test("Android mode reports the SDK and device-tools repair actions", () => { + const checks = evaluateDoctor({ ...ready, androidSdkAvailable: false, adbAvailable: false }, "android"); + assert.deepEqual( + checks.filter((check) => !check.ok).map((check) => check.name), + ["Android SDK", "Android device tools"], + ); + assert.equal(checks.some((check) => check.name === "Xcode"), false); +}); + +test("Android mode does not claim readiness without Java and a target", () => { + const checks = evaluateDoctor({ ...ready, androidJavaVersion: "", androidTargetAvailable: false }, "android"); + assert.deepEqual( + checks.filter((check) => !check.ok).map((check) => check.name), + ["Android Java", "Android target"], + ); +}); + +test("physical-iPhone detection accepts Apple's current table order", () => { + const output = [ + "Name Identifier State Model", + "iPhone device-id available (paired) iPhone 16 Pro (iPhone17,1)", + ].join("\n"); + assert.equal(hasPairedIphone(output), true); +}); + +test("physical-iPhone detection ignores a paired iPad with an iPhone device name", () => { + const output = "iPhone device-id available (paired) iPad Pro 11-inch (iPad17,1)"; + assert.equal(hasPairedIphone(output), false); +}); + +test("native installer selects only paired physical iPhones and prefers the latest", () => { + const device = (type, date, pairingState = "paired") => ({ + connectionProperties: { pairingState, lastConnectionDate: date }, + hardwareProperties: { deviceType: type, reality: "physical" }, + }); + const olderIphone = device("iPhone", "2026-01-01T00:00:00Z"); + const newerIphone = device("iPhone", "2026-07-01T00:00:00Z"); + assert.deepEqual( + pairedPhysicalIphones([ + olderIphone, + device("iPad", "2026-08-01T00:00:00Z"), + newerIphone, + device("iPhone", "2026-09-01T00:00:00Z", "unpaired"), + ]), + [newerIphone, olderIphone], + ); +}); + +test("native installer extracts the signing team and builds an encoded deep link", () => { + assert.equal( + appleTeamIdFromCertificateSubject("subject=UID=person, CN=Apple Development: Person, OU=ABCDEFGHIJ, O=Person, C=US"), + "ABCDEFGHIJ", + ); + assert.equal( + companionDeepLink("https://mac.example.ts.net:8445"), + "t4companion://?address=https%3A%2F%2Fmac.example.ts.net%3A8445", + ); +}); + +test("native installer parses safe workflow switches", () => { + assert.deepEqual(parseArguments(["--device", "phone-id", "--reuse-build", "--no-launch"]), { + device: "phone-id", + team: "", + url: "", + reuseBuild: true, + launchOnly: false, + noLaunch: true, + help: false, + }); + assert.throws(() => parseArguments(["--mystery"]), /Unknown option/u); + assert.throws(() => parseArguments(["--launch-only", "--no-launch"]), /cannot be used together/u); + assert.equal(launchFailureIsLocked("request denied: Locked"), true); +}); + +test("Android installer selects connected devices from adb output", () => { + const output = [ + "List of devices attached", + "emulator-5554 device product:sdk_phone model:Pixel_8 device:emu64a transport_id:1", + "offline-phone offline transport_id:2", + ].join("\n"); + assert.deepEqual(parseAdbDevices(output), [{ serial: "emulator-5554", model: "Pixel 8" }]); +}); + +test("Android installer parses the repeatable build workflow", () => { + assert.deepEqual(parseAndroidArguments(["--device", "emulator-5554", "--reuse-build", "--no-launch"]), { + device: "emulator-5554", + avd: "T4_Pixel_API_36", + url: "", + reuseBuild: true, + launchOnly: false, + noLaunch: true, + help: false, + }); + assert.throws(() => parseAndroidArguments(["--mystery"]), /Unknown option/u); + assert.throws(() => parseAndroidArguments(["--launch-only", "--no-launch"]), /cannot be used together/u); +}); + +test("Android installer recognizes a standard SDK", () => { + const existing = new Set([ + "/tmp/fixture/Library/Android/sdk/platforms", + ]); + const pathExists = (path) => existing.has(path); + assert.equal(resolveAndroidSdkRoot({ ANDROID_HOME: "/missing" }, "/tmp/fixture", pathExists), "/tmp/fixture/Library/Android/sdk"); +}); diff --git a/apps/companion/scripts/doctor.mjs b/apps/companion/scripts/doctor.mjs new file mode 100644 index 00000000..9bb10af6 --- /dev/null +++ b/apps/companion/scripts/doctor.mjs @@ -0,0 +1,201 @@ +#!/usr/bin/env node + +import { existsSync } from "node:fs"; +import { homedir } from "node:os"; +import { delimiter, join } from "node:path"; +import { spawnSync } from "node:child_process"; +import process from "node:process"; +import { pathToFileURL } from "node:url"; + +function run(command, args = []) { + const result = spawnSync(command, args, { encoding: "utf8", timeout: 15_000 }); + return { + ok: result.status === 0, + output: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(), + }; +} + +function executableOnPath(name, pathValue = process.env.PATH ?? "") { + return pathValue + .split(delimiter) + .filter(Boolean) + .some((directory) => existsSync(join(directory, name))); +} + +function androidSdkRoot(environment = process.env, userHome = homedir()) { + const candidates = [ + environment.ANDROID_HOME, + environment.ANDROID_SDK_ROOT, + join(userHome, "Library", "Android", "sdk"), + join(userHome, "Android", "Sdk"), + "/opt/homebrew/share/android-commandlinetools", + "/usr/local/share/android-commandlinetools", + ]; + return candidates.find((candidate) => typeof candidate === "string" && existsSync(candidate)); +} + +function androidJavaVersion(environment = process.env) { + const homes = [ + environment.JAVA_HOME, + "/opt/homebrew/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home", + "/usr/local/opt/openjdk@17/libexec/openjdk.jdk/Contents/Home", + "/Applications/Android Studio.app/Contents/jbr/Contents/Home", + ]; + for (const home of homes) { + if (typeof home !== "string") continue; + const java = join(home, "bin", "java"); + if (!existsSync(java)) continue; + const result = run(java, ["-version"]); + const major = Number(/version "(\d+)/u.exec(result.output)?.[1] ?? 0); + if (major === 17 || major === 21) return String(major); + } + return ""; +} + +export function hasPairedIphone(deviceOutput) { + return deviceOutput + .split("\n") + .some((line) => /available \(paired\)/u.test(line) && /\(iPhone\d+,\d+\)/u.test(line)); +} + +export function evaluateDoctor(snapshot, platform = "all") { + const checks = []; + const includeIos = platform === "all" || platform === "ios"; + const includeAndroid = platform === "all" || platform === "android"; + checks.push({ + name: "Node 24", + ok: snapshot.nodeMajor === 24, + detail: snapshot.nodeMajor === 24 ? snapshot.nodeVersion : `found ${snapshot.nodeVersion}; run through mise Node 24`, + }); + checks.push({ + name: "Checkout path", + ok: !snapshot.checkoutHasSpaces, + detail: snapshot.checkoutHasSpaces + ? "move this checkout to a path without spaces before generating iOS files" + : "safe for Expo native generation", + }); + checks.push({ + name: "Tailscale host", + ok: snapshot.tailnetName.endsWith(".ts.net"), + detail: snapshot.tailnetName || "connect Tailscale on this Mac", + }); + + if (includeIos) { + checks.push({ + name: "Xcode", + ok: snapshot.xcodeAvailable, + detail: snapshot.xcodeAvailable ? snapshot.xcodeVersion : "install Xcode and select its command-line tools", + }); + checks.push({ + name: "Physical iPhone", + ok: snapshot.iphoneAvailable, + detail: snapshot.iphoneAvailable ? "paired and available" : "connect, unlock, and trust an iPhone", + }); + checks.push({ + name: "Apple development signing", + ok: snapshot.appleDevelopmentIdentity, + detail: snapshot.appleDevelopmentIdentity + ? "development certificate is available" + : "open Xcode Settings > Accounts and create an Apple Development certificate", + }); + } + + if (includeAndroid) { + checks.push({ + name: "Android SDK", + ok: snapshot.androidSdkAvailable, + detail: snapshot.androidSdkAvailable ? snapshot.androidSdkRoot : "install Android Studio and its SDK", + }); + checks.push({ + name: "Android device tools", + ok: snapshot.adbAvailable, + detail: snapshot.adbAvailable ? "adb is available" : "install Android SDK Platform-Tools", + }); + checks.push({ + name: "Android Java", + ok: snapshot.androidJavaVersion === "17" || snapshot.androidJavaVersion === "21", + detail: snapshot.androidJavaVersion ? `Java ${snapshot.androidJavaVersion}` : "install openjdk@17", + }); + checks.push({ + name: "Android target", + ok: snapshot.androidTargetAvailable, + detail: snapshot.androidTargetAvailable ? "connected device or configured emulator is available" : "connect a phone or create an Android virtual device", + }); + } + return checks; +} + +export function collectSnapshot() { + const xcode = run("xcodebuild", ["-version"]); + const devices = run("xcrun", ["devicectl", "list", "devices"]); + const identities = run("security", ["find-identity", "-v", "-p", "codesigning"]); + const tailscale = run("tailscale", ["status", "--json"]); + let tailnetName = ""; + if (tailscale.ok) { + try { + const parsed = JSON.parse(tailscale.output); + tailnetName = String(parsed?.Self?.DNSName ?? "").replace(/\.$/u, ""); + } catch { + // The check below supplies the user-facing repair action. + } + } + const sdkRoot = androidSdkRoot(); + const adb = executableOnPath("adb") + ? "adb" + : sdkRoot !== undefined && existsSync(join(sdkRoot, "platform-tools", "adb")) + ? join(sdkRoot, "platform-tools", "adb") + : ""; + const adbDevices = adb ? run(adb, ["devices"]) : { ok: false, output: "" }; + const emulator = sdkRoot !== undefined && existsSync(join(sdkRoot, "emulator", "emulator")) + ? join(sdkRoot, "emulator", "emulator") + : executableOnPath("emulator") + ? "emulator" + : ""; + const virtualDevices = emulator ? run(emulator, ["-list-avds"]) : { ok: false, output: "" }; + return { + nodeMajor: Number(process.versions.node.split(".")[0]), + nodeVersion: process.versions.node, + checkoutHasSpaces: process.cwd().includes(" "), + tailnetName, + xcodeAvailable: xcode.ok, + xcodeVersion: xcode.output.split("\n")[0] ?? "Xcode", + iphoneAvailable: devices.ok && hasPairedIphone(devices.output), + appleDevelopmentIdentity: + identities.ok && /"(?:Apple Development|iPhone Developer):/u.test(identities.output), + androidSdkAvailable: sdkRoot !== undefined, + androidSdkRoot: sdkRoot ?? "", + adbAvailable: + executableOnPath("adb") || (sdkRoot !== undefined && existsSync(join(sdkRoot, "platform-tools", "adb"))), + androidJavaVersion: androidJavaVersion(), + androidTargetAvailable: + (adbDevices.ok && adbDevices.output.split("\n").some((line) => /\tdevice(?:\s|$)/u.test(line))) + || (virtualDevices.ok && virtualDevices.output.trim() !== ""), + }; +} + +function selectedPlatform(argumentsList) { + const index = argumentsList.indexOf("--platform"); + const value = index === -1 ? "all" : argumentsList[index + 1]; + if (value !== "all" && value !== "ios" && value !== "android") { + throw new Error("Use --platform ios, android, or all."); + } + return value; +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + let platform; + try { + platform = selectedPlatform(process.argv.slice(2)); + } catch (error) { + console.error(error instanceof Error ? error.message : String(error)); + process.exitCode = 2; + } + if (platform) { + const checks = evaluateDoctor(collectSnapshot(), platform); + console.log(`T4 Companion readiness (${platform})\n`); + for (const check of checks) console.log(`${check.ok ? "PASS" : "FIX "} ${check.name}: ${check.detail}`); + const failing = checks.filter((check) => !check.ok); + console.log(failing.length === 0 ? "\nReady to build." : `\n${failing.length} item${failing.length === 1 ? "" : "s"} need attention.`); + process.exitCode = failing.length === 0 ? 0 : 1; + } +} diff --git a/apps/companion/scripts/ios-device.mjs b/apps/companion/scripts/ios-device.mjs new file mode 100644 index 00000000..17ada49a --- /dev/null +++ b/apps/companion/scripts/ios-device.mjs @@ -0,0 +1,274 @@ +#!/usr/bin/env node + +import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { dirname, join, resolve } from "node:path"; +import { spawnSync } from "node:child_process"; +import process from "node:process"; +import { fileURLToPath, pathToFileURL } from "node:url"; + +const packageDirectory = resolve(dirname(fileURLToPath(import.meta.url)), ".."); +const repositoryRoot = resolve(packageDirectory, "../.."); +const appConfig = JSON.parse(readFileSync(join(packageDirectory, "app.json"), "utf8")).expo; +const bundleIdentifier = appConfig.ios.bundleIdentifier; +const scheme = appConfig.scheme; +const workspaceName = "T4Companion"; +const derivedDataPath = join(packageDirectory, ".expo", "ios-device-build"); +const builtAppPath = join(derivedDataPath, "Build", "Products", "Release-iphoneos", `${workspaceName}.app`); + +function run(command, args, options = {}) { + return spawnSync(command, args, { + cwd: options.cwd ?? packageDirectory, + encoding: "utf8", + input: options.input, + stdio: options.stdio ?? "pipe", + timeout: options.timeout ?? 30_000, + maxBuffer: options.maxBuffer ?? 20 * 1024 * 1024, + }); +} + +function commandOutput(result) { + return `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(); +} + +function runOrThrow(command, args, options = {}) { + const result = run(command, args, options); + if (result.status !== 0) { + throw new Error(commandOutput(result) || `${command} exited with status ${result.status ?? "unknown"}`); + } + return result; +} + +function runVisible(command, args, options = {}) { + const result = run(command, args, { + ...options, + stdio: "inherit", + timeout: options.timeout ?? 20 * 60_000, + }); + if (result.status !== 0) throw new Error(`${command} exited with status ${result.status ?? "unknown"}`); +} + +export function pairedPhysicalIphones(devices) { + return devices + .filter((device) => + device?.connectionProperties?.pairingState === "paired" + && device?.hardwareProperties?.deviceType === "iPhone" + && device?.hardwareProperties?.reality === "physical") + .sort((left, right) => { + const leftDate = Date.parse(left?.connectionProperties?.lastConnectionDate ?? "") || 0; + const rightDate = Date.parse(right?.connectionProperties?.lastConnectionDate ?? "") || 0; + return rightDate - leftDate; + }); +} + +export function appleTeamIdFromCertificateSubject(subject) { + return /(?:^|,)\s*OU\s*=\s*([A-Z0-9]{10})(?:,|$)/u.exec(subject)?.[1] ?? ""; +} + +export function companionDeepLink(address, appScheme = scheme) { + return `${appScheme}://?address=${encodeURIComponent(address)}`; +} + +export function launchFailureIsLocked(output) { + return /Locked|could not be unlocked/u.test(output); +} + +function listDevices() { + const temporaryDirectory = mkdtempSync(join(tmpdir(), "t4-companion-devices-")); + const jsonPath = join(temporaryDirectory, "devices.json"); + try { + runOrThrow("xcrun", ["devicectl", "list", "devices", "--json-output", jsonPath, "--quiet"], { + timeout: 60_000, + }); + return JSON.parse(readFileSync(jsonPath, "utf8"))?.result?.devices ?? []; + } finally { + rmSync(temporaryDirectory, { recursive: true, force: true }); + } +} + +function resolveDevice(requestedIdentifier) { + const iphones = pairedPhysicalIphones(listDevices()); + if (requestedIdentifier) { + const matched = iphones.find((device) => + device.identifier === requestedIdentifier || device?.hardwareProperties?.udid === requestedIdentifier); + if (!matched) throw new Error(`No paired physical iPhone matched ${requestedIdentifier}.`); + return matched; + } + if (iphones.length === 0) { + throw new Error("No paired physical iPhone found. Connect, unlock, and trust the iPhone first."); + } + return iphones[0]; +} + +function resolveAppleTeamId(explicitTeamId) { + if (explicitTeamId) return explicitTeamId; + if (process.env.T4_APPLE_TEAM_ID) return process.env.T4_APPLE_TEAM_ID; + const certificate = runOrThrow("security", ["find-certificate", "-c", "Apple Development", "-p"]); + const subject = runOrThrow("openssl", ["x509", "-noout", "-subject"], { input: certificate.stdout }); + const teamId = appleTeamIdFromCertificateSubject(subject.stdout); + if (!teamId) throw new Error("Could not infer the Apple team. Re-run with --team YOUR_TEAM_ID."); + return teamId; +} + +function resolveTailnetAddress(explicitAddress) { + if (explicitAddress) return explicitAddress.replace(/\/$/u, ""); + const status = runOrThrow("tailscale", ["status", "--json"]); + const dnsName = String(JSON.parse(status.stdout)?.Self?.DNSName ?? "").replace(/\.$/u, ""); + if (!dnsName.endsWith(".ts.net")) throw new Error("This Mac does not have a stable Tailscale DNS name."); + return `https://${dnsName}:8445`; +} + +function argumentValue(argumentsList, name) { + const index = argumentsList.indexOf(name); + if (index === -1) return ""; + const value = argumentsList[index + 1]; + if (!value || value.startsWith("--")) throw new Error(`${name} needs a value.`); + return value; +} + +function printUsage() { + console.log(`Build, install, and open T4 Companion on a physical iPhone. + +Usage: + pnpm --filter @t4-code/companion ios:device + +Options: + --device ID Use a specific CoreDevice identifier or iPhone UDID + --team TEAM_ID Override the Apple team inferred from the development certificate + --url URL Override the stable Tailnet address inferred from Tailscale + --reuse-build Reinstall the last build without rebuilding it + --launch-only Open the installed app without rebuilding or reinstalling + --no-launch Install the app but do not open it + --help Show this help + +Environment: + T4_APPLE_TEAM_ID Apple team fallback for unusual certificate setups`); +} + +export function parseArguments(argumentsList) { + const known = new Set(["--device", "--team", "--url", "--reuse-build", "--launch-only", "--no-launch", "--help"]); + for (let index = 0; index < argumentsList.length; index += 1) { + const argument = argumentsList[index]; + if (!known.has(argument)) throw new Error(`Unknown option: ${argument}`); + if (["--device", "--team", "--url"].includes(argument)) index += 1; + } + const parsed = { + device: argumentValue(argumentsList, "--device"), + team: argumentValue(argumentsList, "--team"), + url: argumentValue(argumentsList, "--url"), + reuseBuild: argumentsList.includes("--reuse-build"), + launchOnly: argumentsList.includes("--launch-only"), + noLaunch: argumentsList.includes("--no-launch"), + help: argumentsList.includes("--help"), + }; + if (parsed.launchOnly && parsed.noLaunch) throw new Error("--launch-only and --no-launch cannot be used together."); + return parsed; +} + +function ensureNativeWorkspace() { + const workspacePath = join(packageDirectory, "ios", `${workspaceName}.xcworkspace`); + if (existsSync(workspacePath)) return; + console.log("Generating the native iOS project and installing CocoaPods…"); + runVisible("pnpm", ["--filter", "@t4-code/companion", "exec", "expo", "prebuild", "--platform", "ios"], { + cwd: repositoryRoot, + }); +} + +function delay(milliseconds) { + return new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds)); +} + +async function launchCompanion(deviceIdentifier, deepLink, waitMilliseconds = 60_000) { + const deadline = Date.now() + waitMilliseconds; + let announcedWait = false; + while (true) { + const launch = run("xcrun", [ + "devicectl", "device", "process", "launch", + "--device", deviceIdentifier, + "--terminate-existing", + "--payload-url", deepLink, + bundleIdentifier, + ], { timeout: 60_000 }); + if (launch.status === 0) return commandOutput(launch); + const output = commandOutput(launch); + if (!launchFailureIsLocked(output) || Date.now() >= deadline) throw new Error(output || "The app installed but did not open."); + if (!announcedWait) { + announcedWait = true; + console.log("The iPhone is locked. Unlock it now; waiting up to one minute…"); + } + await delay(2_000); + } +} + +async function main(argumentsList) { + const options = parseArguments(argumentsList); + if (options.help) { + printUsage(); + return; + } + const device = resolveDevice(options.device); + const deviceIdentifier = device.identifier; + const deviceUdid = device.hardwareProperties.udid; + const model = device.hardwareProperties.marketingName; + const teamId = options.launchOnly ? "" : resolveAppleTeamId(options.team); + const address = resolveTailnetAddress(options.url); + const deepLink = companionDeepLink(address); + + console.log(`iPhone: ${model} (${deviceUdid})`); + if (teamId !== "") console.log(`Apple team: ${teamId}`); + console.log(`T4 host: ${address}`); + + if (!options.launchOnly && !options.reuseBuild) { + ensureNativeWorkspace(); + console.log("Building a signed Release app…"); + runOrThrow("xcodebuild", [ + "-workspace", join(packageDirectory, "ios", `${workspaceName}.xcworkspace`), + "-scheme", workspaceName, + "-configuration", "Release", + "-destination", `platform=iOS,id=${deviceUdid}`, + "-derivedDataPath", derivedDataPath, + "-allowProvisioningUpdates", + "-allowProvisioningDeviceRegistration", + `DEVELOPMENT_TEAM=${teamId}`, + "CODE_SIGN_STYLE=Automatic", + "build", + "-quiet", + ], { timeout: 20 * 60_000 }); + console.log("Signed Release build finished."); + } else if (!options.launchOnly && !existsSync(builtAppPath)) { + throw new Error(`No reusable build exists at ${builtAppPath}. Run once without --reuse-build.`); + } + + if (!options.launchOnly) { + console.log("Installing T4 Companion…"); + runVisible("xcrun", ["devicectl", "device", "install", "app", "--device", deviceIdentifier, builtAppPath], { + timeout: 5 * 60_000, + }); + } + + if (options.noLaunch) { + console.log(`Installed. Open T4 Companion on the iPhone; its host is ${address}`); + return; + } + + console.log("Opening T4 Companion with the saved Tailnet address…"); + try { + console.log(await launchCompanion(deviceIdentifier, deepLink)); + } catch (caught) { + const output = caught instanceof Error ? caught.message : String(caught); + if (launchFailureIsLocked(output)) { + throw new Error(`The app is installed, but the iPhone stayed locked. Unlock it and run:\n\n pnpm --filter @t4-code/companion ios:device --launch-only`); + } + throw caught; + } + console.log("T4 Companion is installed, open, and configured."); +} + +if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) { + try { + await main(process.argv.slice(2)); + } catch (error) { + console.error(`\nT4 iPhone setup stopped: ${error instanceof Error ? error.message : String(error)}`); + process.exitCode = 1; + } +} diff --git a/apps/companion/src/app/(tabs)/_layout.tsx b/apps/companion/src/app/(tabs)/_layout.tsx new file mode 100644 index 00000000..09931279 --- /dev/null +++ b/apps/companion/src/app/(tabs)/_layout.tsx @@ -0,0 +1,27 @@ +import { Tabs } from "expo-router"; +import { Text, type ColorValue } from "react-native"; + +import { colors } from "@/theme"; + +function TabIcon({ glyph, color }: { glyph: string; color: ColorValue }) { + return {glyph}; +} + +export default function TabLayout() { + return ( + + }} /> + }} /> + }} /> + + ); +} diff --git a/apps/companion/src/app/(tabs)/hosts.tsx b/apps/companion/src/app/(tabs)/hosts.tsx new file mode 100644 index 00000000..e9f8e189 --- /dev/null +++ b/apps/companion/src/app/(tabs)/hosts.tsx @@ -0,0 +1,80 @@ +import { Redirect, useRouter } from "expo-router"; +import { Alert, Linking, Pressable, StyleSheet, Text, View } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { ConnectionPill } from "@/components/connection-pill"; +import { useAttentionNotifications } from "@/runtime/attention-notifications"; +import { useCompanionRuntime } from "@/runtime/companion-runtime"; +import { colors, radius, spacing } from "@/theme"; + +export default function HostScreen() { + const runtime = useCompanionRuntime(); + const notifications = useAttentionNotifications(); + const router = useRouter(); + if (runtime.host === null || runtime.connection === "pairing") return ; + const forget = () => Alert.alert( + "Forget this host?", + "The Tailnet address and paired device credential will be removed from this phone.", + [ + { text: "Cancel", style: "cancel" }, + { text: "Forget", style: "destructive", onPress: () => void runtime.forgetHost().then(() => router.replace("/")) }, + ], + ); + const updateNotifications = () => { + if (notifications.status === "denied") { + void Linking.openSettings(); + return; + } + void notifications.setEnabled(notifications.status !== "enabled"); + }; + const notificationLabel = notifications.status === "enabled" + ? "Disable attention notifications" + : notifications.status === "denied" + ? "Open notification settings" + : "Enable attention notifications"; + const notificationDisabled = notifications.status === "loading" || notifications.status === "unavailable"; + return ( + + Host + + {runtime.host.label} + {runtime.host.origin} + Profile{runtime.host.profileId} + Connection{runtime.connection === "ready" ? "Live" : "Reconnecting"} + + {runtime.error !== null && {runtime.error}} + Reconnect now + + {notificationLabel} + + {notifications.status === "enabled" && Best-effort alerts appear while the app remains connected in the background. iOS can suspend direct Tailnet connections.} + {notifications.status === "denied" && Allow notifications in system settings, then return here.} + {notifications.error !== null && {notifications.error}} + Forget this host + T4 Companion reaches this computer only through its private Tailscale address. Funnel is not supported. + + ); +} + +const styles = StyleSheet.create({ + safe: { flex: 1, backgroundColor: colors.background, paddingHorizontal: spacing.md }, + header: { minHeight: 72, flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, + heading: { color: colors.text, fontSize: 32, fontWeight: "800", letterSpacing: -1 }, + card: { backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: radius.md, padding: spacing.md, gap: spacing.sm }, + hostName: { color: colors.text, fontSize: 18, fontWeight: "700" }, + address: { color: colors.textMuted, fontSize: 13 }, + row: { flexDirection: "row", justifyContent: "space-between", borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: colors.border, paddingTop: spacing.sm }, + key: { color: colors.textMuted, fontSize: 14 }, + value: { color: colors.text, fontSize: 14, fontWeight: "600" }, + live: { color: colors.green }, + button: { height: 50, borderRadius: radius.md, backgroundColor: colors.blue, alignItems: "center", justifyContent: "center", marginTop: spacing.lg }, + buttonLabel: { color: "white", fontSize: 16, fontWeight: "700" }, + secondaryButton: { height: 50, borderRadius: radius.md, borderWidth: 1, borderColor: colors.border, alignItems: "center", justifyContent: "center", marginTop: spacing.sm }, + secondaryLabel: { color: colors.text, fontSize: 15, fontWeight: "600" }, + dangerButton: { height: 50, borderRadius: radius.md, borderWidth: 1, borderColor: "#5d2a2d", alignItems: "center", justifyContent: "center", marginTop: spacing.sm }, + dangerLabel: { color: colors.red, fontSize: 16, fontWeight: "600" }, + disabled: { opacity: 0.4 }, + error: { color: colors.red, fontSize: 13, lineHeight: 18, marginTop: spacing.md }, + hint: { color: colors.textMuted, fontSize: 12, lineHeight: 17, marginTop: spacing.xs }, + footnote: { color: colors.textDim, fontSize: 12, lineHeight: 18, marginTop: spacing.lg }, +}); diff --git a/apps/companion/src/app/(tabs)/index.tsx b/apps/companion/src/app/(tabs)/index.tsx new file mode 100644 index 00000000..093f51c7 --- /dev/null +++ b/apps/companion/src/app/(tabs)/index.tsx @@ -0,0 +1,78 @@ +import { Redirect, useRouter } from "expo-router"; +import { useMemo, useState } from "react"; +import { RefreshControl, ScrollView, StyleSheet, Text, View } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { AttentionRow } from "@/components/attention-row"; +import { ConnectionPill } from "@/components/connection-pill"; +import { SessionRow } from "@/components/session-row"; +import { useCompanionRuntime } from "@/runtime/companion-runtime"; +import { attentionFrom, sessionsFrom } from "@/runtime/session-view"; +import { colors, radius, spacing } from "@/theme"; + +export default function InboxScreen() { + const router = useRouter(); + const runtime = useCompanionRuntime(); + const [refreshing, setRefreshing] = useState(false); + const attention = useMemo(() => attentionFrom(runtime.projection), [runtime.projection]); + const sessions = useMemo( + () => sessionsFrom(runtime.projection).filter((session) => session.archivedAt === undefined && session.status === "active").slice(0, 5), + [runtime.projection], + ); + if (runtime.host === null || runtime.connection === "pairing") return ; + + const open = (sessionId: string) => { + const session = sessionsFrom(runtime.projection).find((item) => String(item.sessionId) === sessionId); + if (session !== undefined) void runtime.openSession(session).catch(() => undefined); + router.push({ pathname: "/session/[id]", params: { id: sessionId } }); + }; + const refresh = async () => { + setRefreshing(true); + try { await runtime.refresh(); } catch { /* surfaced by the shared runtime */ } + finally { setRefreshing(false); } + }; + + return ( + + void refresh()} />} + > + + Needs you + + + {runtime.error !== null && ( + {runtime.error} Tap to retry. + )} + + Needs your attention + + {attention.length === 0 ? ( + Nothing is waitingQuestions and approvals from your active work will appear here. + ) : attention.map((item) => open(String(item.session.sessionId))} />)} + + + Running + + {sessions.length === 0 ? ( + No sessions runningStart work from OMP or T4 Code on your computer. + ) : sessions.map((session) => open(String(session.sessionId))} />)} + + + + ); +} + +const styles = StyleSheet.create({ + safe: { flex: 1, backgroundColor: colors.background }, + content: { paddingHorizontal: spacing.md, paddingBottom: spacing.xxl }, + header: { minHeight: 72, flexDirection: "row", alignItems: "center", justifyContent: "space-between", gap: spacing.sm }, + heading: { color: colors.text, fontSize: 32, fontWeight: "800", letterSpacing: -1 }, + sectionTitle: { color: colors.text, fontSize: 16, fontWeight: "700", marginTop: spacing.lg, marginBottom: spacing.sm }, + group: { overflow: "hidden", backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, borderRadius: radius.md }, + empty: { padding: spacing.lg, gap: spacing.xs }, + emptyTitle: { color: colors.text, fontSize: 15, fontWeight: "600" }, + emptyBody: { color: colors.textMuted, fontSize: 13, lineHeight: 19 }, + error: { color: colors.red, backgroundColor: "#251416", borderRadius: radius.sm, padding: spacing.sm, fontSize: 13, lineHeight: 18 }, +}); diff --git a/apps/companion/src/app/(tabs)/sessions.tsx b/apps/companion/src/app/(tabs)/sessions.tsx new file mode 100644 index 00000000..4d389902 --- /dev/null +++ b/apps/companion/src/app/(tabs)/sessions.tsx @@ -0,0 +1,68 @@ +import { Redirect, useRouter } from "expo-router"; +import { useMemo, useState } from "react"; +import { FlatList, StyleSheet, Text, TextInput, View } from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { ConnectionPill } from "@/components/connection-pill"; +import { SessionRow } from "@/components/session-row"; +import { useCompanionRuntime } from "@/runtime/companion-runtime"; +import { projectName, sessionsFrom } from "@/runtime/session-view"; +import { colors, radius, spacing } from "@/theme"; + +export default function SessionsScreen() { + const runtime = useCompanionRuntime(); + const router = useRouter(); + const [query, setQuery] = useState(""); + const sessions = useMemo(() => { + const needle = query.trim().toLowerCase(); + return sessionsFrom(runtime.projection).filter((session) => + session.archivedAt === undefined && + (needle === "" || session.title.toLowerCase().includes(needle) || projectName(session).toLowerCase().includes(needle)), + ); + }, [query, runtime.projection]); + if (runtime.host === null || runtime.connection === "pairing") return ; + + return ( + + + Sessions + + + + `${String(session.hostId)}:${String(session.sessionId)}`} + ListEmptyComponent={No matching sessions.} + renderItem={({ item }) => ( + { + router.push({ pathname: "/session/[id]", params: { id: String(item.sessionId) } }); + }} + /> + )} + style={styles.listFrame} + /> + + ); +} + +const styles = StyleSheet.create({ + safe: { flex: 1, backgroundColor: colors.background, paddingHorizontal: spacing.md }, + header: { minHeight: 72, flexDirection: "row", alignItems: "center", justifyContent: "space-between" }, + heading: { color: colors.text, fontSize: 32, fontWeight: "800", letterSpacing: -1 }, + search: { height: 44, borderRadius: radius.md, backgroundColor: colors.surface, borderWidth: 1, borderColor: colors.border, color: colors.text, paddingHorizontal: spacing.md, fontSize: 15, marginBottom: spacing.md }, + listFrame: { borderWidth: 1, borderColor: colors.border, borderRadius: radius.md, backgroundColor: colors.surface, overflow: "hidden" }, + list: { paddingBottom: spacing.xl }, + emptyList: { flex: 1, alignItems: "center", justifyContent: "center" }, + empty: { color: colors.textMuted, fontSize: 14 }, +}); diff --git a/apps/companion/src/app/_layout.tsx b/apps/companion/src/app/_layout.tsx new file mode 100644 index 00000000..e729d852 --- /dev/null +++ b/apps/companion/src/app/_layout.tsx @@ -0,0 +1,30 @@ +import { Stack } from "expo-router"; +import { StatusBar } from "expo-status-bar"; + +import { AttentionNotificationsProvider } from "@/runtime/attention-notifications"; +import { CompanionRuntimeProvider } from "@/runtime/companion-runtime"; +import { colors } from "@/theme"; + +export const unstable_settings = { anchor: "index" }; + +export default function RootLayout() { + return ( + + + + + + + + + + + ); +} diff --git a/apps/companion/src/app/index.tsx b/apps/companion/src/app/index.tsx new file mode 100644 index 00000000..5e5219da --- /dev/null +++ b/apps/companion/src/app/index.tsx @@ -0,0 +1,210 @@ +import { Redirect, useLocalSearchParams, useRouter } from "expo-router"; +import { useEffect, useState } from "react"; +import { SafeAreaView } from "react-native-safe-area-context"; +import { + ActivityIndicator, + KeyboardAvoidingView, + Platform, + Pressable, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; + +import { useCompanionRuntime } from "@/runtime/companion-runtime"; +import { colors, radius, spacing } from "@/theme"; + +function Brand() { + return ( + + T4 + Companion + + ); +} + +function TrustNote({ symbol, title, body }: { symbol: string; title: string; body: string }) { + return ( + + {symbol} + + {title} + {body} + + + ); +} + +function ConnectScreen({ initialAddress = "", initialMessage = null }: { + initialAddress?: string; + initialMessage?: string | null; +}) { + const { configureHost, error } = useCompanionRuntime(); + const [address, setAddress] = useState(initialAddress); + const [message, setMessage] = useState(initialMessage); + const [submitting, setSubmitting] = useState(false); + + const submit = async () => { + if (submitting) return; + setSubmitting(true); + setMessage(null); + try { + await configureHost(address); + } catch (caught) { + setMessage(caught instanceof Error ? caught.message : "Check the address and try again."); + setSubmitting(false); + } + }; + + return ( + + + + + Mission control for your AI coding sessions—on your terms. + T4 connects to your computer over Tailscale. Your projects and agents stay on your machine. + + + Tailnet HTTPS address + void submit()} + placeholder="your-mac.tailnet.ts.net:8445" + placeholderTextColor={colors.textDim} + returnKeyType="go" + style={styles.input} + value={address} + /> + {(message ?? error) !== null && {message ?? error}} + void submit()} + style={({ pressed }) => [styles.primaryButton, pressed && styles.primaryPressed, submitting && styles.disabled]} + > + {submitting ? : Connect securely} + + + + + + + + + ); +} + +function PairScreen() { + const { pair, error, forgetHost } = useCompanionRuntime(); + const [code, setCode] = useState(""); + const [message, setMessage] = useState(null); + const [submitting, setSubmitting] = useState(false); + const submit = async () => { + setSubmitting(true); + setMessage(null); + try { + await pair(code); + } catch (caught) { + setMessage(caught instanceof Error ? caught.message : "Pairing failed."); + setSubmitting(false); + } + }; + return ( + + + + Pair this phone + Enter the six-digit code shown by T4 Code on your computer. + void submit()} + placeholder="000000" + placeholderTextColor={colors.textDim} + style={styles.codeInput} + value={code} + /> + {(message ?? error) !== null && {message ?? error}} + void submit()} style={[styles.primaryButton, (code.length !== 6 || submitting) && styles.disabled]}> + {submitting ? : Pair securely} + + void forgetHost()} style={styles.textButton}>Use a different host + + + ); +} + +function LinkedHostScreen({ address, profile }: { address: string; profile?: string }) { + const { configureHost } = useCompanionRuntime(); + const router = useRouter(); + const [message, setMessage] = useState(null); + + useEffect(() => { + let active = true; + void configureHost(address, profile).then( + () => { + if (active) router.replace("/(tabs)"); + }, + (caught: unknown) => { + if (active) setMessage(caught instanceof Error ? caught.message : "Check the address and try again."); + }, + ); + return () => { + active = false; + }; + }, [address, configureHost, profile, router]); + + if (message !== null) return ; + return ; +} + +export default function IndexScreen() { + const { host, connection } = useCompanionRuntime(); + const params = useLocalSearchParams<{ address?: string; profile?: string }>(); + const linkedAddress = Array.isArray(params.address) ? params.address[0] : params.address; + const linkedProfile = Array.isArray(params.profile) ? params.profile[0] : params.profile; + if (connection === "loading") { + return ; + } + if (linkedAddress !== undefined) return ; + if (host === null) return ; + if (connection === "pairing") return ; + return ; +} + +const styles = StyleSheet.create({ + safe: { flex: 1, backgroundColor: colors.background }, + loading: { alignItems: "center", justifyContent: "center", gap: spacing.xl }, + connectPage: { flex: 1, paddingHorizontal: spacing.lg, paddingBottom: spacing.lg, justifyContent: "center" }, + pairPage: { flex: 1, padding: spacing.lg, justifyContent: "center" }, + brand: { alignItems: "center", marginBottom: spacing.xl }, + mark: { color: colors.text, fontSize: 58, fontWeight: "800", letterSpacing: -4 }, + brandLabel: { color: colors.textMuted, fontSize: 17, marginTop: -6 }, + intro: { alignItems: "center", gap: spacing.sm, marginBottom: spacing.xl }, + headline: { color: colors.text, fontSize: 18, lineHeight: 25, fontWeight: "700", textAlign: "center", maxWidth: 330 }, + subhead: { color: colors.textMuted, fontSize: 15, lineHeight: 22, textAlign: "center", maxWidth: 350 }, + form: { gap: spacing.sm }, + label: { color: colors.textMuted, fontSize: 13, fontWeight: "600" }, + input: { height: 54, borderColor: colors.border, borderWidth: 1, borderRadius: radius.md, backgroundColor: colors.surface, color: colors.text, fontSize: 16, paddingHorizontal: spacing.md }, + codeInput: { height: 68, borderColor: colors.border, borderWidth: 1, borderRadius: radius.md, backgroundColor: colors.surface, color: colors.text, fontSize: 30, fontWeight: "700", letterSpacing: 12, textAlign: "center", marginVertical: spacing.xl }, + primaryButton: { minHeight: 54, borderRadius: radius.md, backgroundColor: colors.blue, alignItems: "center", justifyContent: "center", paddingHorizontal: spacing.md }, + primaryPressed: { backgroundColor: colors.bluePressed }, + primaryLabel: { color: "white", fontSize: 17, fontWeight: "700" }, + disabled: { opacity: 0.45 }, + error: { color: colors.red, fontSize: 13, lineHeight: 18 }, + trustList: { borderTopWidth: 1, borderTopColor: colors.border, marginTop: spacing.xl }, + trustRow: { flexDirection: "row", gap: spacing.md, paddingVertical: spacing.md, borderBottomWidth: 1, borderBottomColor: colors.border }, + trustSymbol: { color: colors.textMuted, width: 28, fontSize: 18, textAlign: "center" }, + trustCopy: { flex: 1, gap: 2 }, + trustTitle: { color: colors.text, fontSize: 14, fontWeight: "600" }, + trustBody: { color: colors.textMuted, fontSize: 13, lineHeight: 18 }, + pairTitle: { color: colors.text, fontSize: 28, fontWeight: "700", textAlign: "center", marginBottom: spacing.sm }, + textButton: { alignItems: "center", padding: spacing.md }, + textButtonLabel: { color: colors.blue, fontSize: 15, fontWeight: "600" }, +}); diff --git a/apps/companion/src/app/session/[id].tsx b/apps/companion/src/app/session/[id].tsx new file mode 100644 index 00000000..c7ca7077 --- /dev/null +++ b/apps/companion/src/app/session/[id].tsx @@ -0,0 +1,301 @@ +import type { PendingAttentionItem, SessionRef } from "@t4-code/protocol"; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { + ActivityIndicator, + KeyboardAvoidingView, + Platform, + Pressable, + ScrollView, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; +import { SafeAreaView } from "react-native-safe-area-context"; + +import { TranscriptSpeechButton } from "@/components/transcript-speech-button"; +import { VoiceInputButton } from "@/components/voice-input-button"; +import { useCompanionRuntime } from "@/runtime/companion-runtime"; +import { + canWriteSession, + entryRole, + entryText, + latestAssistantText, + projectName, + sessionsFrom, + transcriptDisplayState, + warmSession, +} from "@/runtime/session-view"; +import { colors, radius, spacing } from "@/theme"; + +function SmallButton({ + children, + destructive = false, + disabled = false, + onPress, +}: { + children: string; + destructive?: boolean; + disabled?: boolean; + onPress: () => void; +}) { + return ( + [styles.smallButton, destructive && styles.denyButton, pressed && styles.pressed, disabled && styles.disabled]} + > + {children} + + ); +} + +function AttentionPanel({ session, item, writable }: { session: SessionRef; item: PendingAttentionItem; writable: boolean }) { + const runtime = useCompanionRuntime(); + const [draft, setDraft] = useState(""); + const [busy, setBusy] = useState(false); + const [error, setError] = useState(null); + const respond = async (value?: string) => { + setBusy(true); + setError(null); + try { await runtime.respond(session, item, value); } + catch (caught) { setError(caught instanceof Error ? caught.message : "T4 did not accept that response."); } + finally { setBusy(false); } + }; + return ( + + + {item.kind === "question" ? "?" : "✓"} + + {item.kind === "question" ? "Agent question" : item.kind === "plan" ? "Plan ready" : item.title} + {item.kind === "question" ? item.question : item.summary} + + + {item.kind === "question" ? ( + <> + {item.options.length > 0 && ( + + {item.options.map((option) => void respond(option.id)}>{option.label})} + + )} + {item.allowText && ( + + + void respond(draft)}>Send + + )} + + ) : ( + + void respond("deny")}>Deny + void respond("approve")}>Approve + + )} + {!writable && Open this session in T4 on your computer to take control before responding.} + {error !== null && {error}} + + ); +} + +export default function SessionScreen() { + const { id } = useLocalSearchParams<{ id: string }>(); + const router = useRouter(); + const runtime = useCompanionRuntime(); + const scrollRef = useRef(null); + const [draft, setDraft] = useState(""); + const [sending, setSending] = useState(false); + const [message, setMessage] = useState(null); + const [openedSessionId, setOpenedSessionId] = useState(null); + const openSession = runtime.openSession; + const session = useMemo( + () => sessionsFrom(runtime.projection).find((item) => String(item.sessionId) === id), + [id, runtime.projection], + ); + const warm = session === undefined ? undefined : warmSession(runtime.projection, String(session.hostId), String(session.sessionId)); + const entries = useMemo( + () => (warm?.entries ?? []).map((entry) => ({ entry, text: entryText(entry.data) })).filter((item) => item.text !== null).slice(-60), + [warm?.entries], + ); + const confirmations = [...(warm?.confirmations.values() ?? [])] as readonly Record[]; + const transcriptState = transcriptDisplayState(openedSessionId === id, entries.length); + const latestAssistantMessage = latestAssistantText(warm?.entries ?? []); + + useEffect(() => { + if (session === undefined) return; + let cancelled = false; + void openSession(session) + .catch((caught: unknown) => { + if (!cancelled) setMessage(caught instanceof Error ? caught.message : "T4 could not open this session."); + }) + .finally(() => { + if (!cancelled) setOpenedSessionId(id); + }); + return () => { + cancelled = true; + }; + }, [id, openSession, session]); + + if (session === undefined) { + return ( + + That session is not available. + router.back()}>Go back + + ); + } + + const writable = canWriteSession(session, warm !== undefined) && runtime.connection === "ready"; + const send = async () => { + if (draft.trim() === "" || sending) return; + const next = draft; + setSending(true); + setMessage(null); + try { + await runtime.sendMessage(session, next); + setDraft(""); + } catch (caught) { + setMessage(caught instanceof Error ? caught.message : "T4 did not accept that message."); + } finally { + setSending(false); + } + }; + + return ( + + + + router.back()} style={styles.backButton}> + + {projectName(session)} + {session.status === "active" ? "● Running" : "Idle"} · {runtime.connection === "ready" ? "Live" : "Reconnecting"} + + + + + scrollRef.current?.scrollToEnd({ animated: false })} + ref={scrollRef} + style={styles.transcript} + contentContainerStyle={styles.transcriptContent} + > + {transcriptState === "loading" ? ( + Loading recent context… + ) : transcriptState === "empty" ? ( + + No recent text was returned. + Refresh the session list, then try opening it again. + + ) : entries.map(({ entry, text }) => { + const role = entryRole(entry.data); + return ( + + {role[0]} + + {role} + {text} + + + ); + })} + + {(session.attention?.pending ?? []).map((item) => )} + {confirmations.map((confirmation) => { + const confirmationId = typeof confirmation.confirmationId === "string" ? confirmation.confirmationId : null; + const commandId = typeof confirmation.commandId === "string" ? confirmation.commandId : null; + if (confirmationId === null || commandId === null) return null; + return ( + + Confirmation required + {typeof confirmation.summary === "string" ? confirmation.summary : "Review this action on your computer."} + + void runtime.decideConfirmation({ session, confirmationId, commandId, decision: "deny" })}>Deny + void runtime.decideConfirmation({ session, confirmationId, commandId, decision: "approve" })}>Approve + + + ); + })} + + + {message !== null && {message}} + {!writable && Active in another app · read-only here} + + + setMessage(error === "" ? null : error)} + value={draft} + /> + void send()} style={[styles.sendButton, (!writable || sending || draft.trim() === "") && styles.disabled]}> + {sending ? : } + + + + + ); +} + +const styles = StyleSheet.create({ + safe: { flex: 1, backgroundColor: colors.background }, + flex: { flex: 1 }, + center: { alignItems: "center", justifyContent: "center", gap: spacing.md, padding: spacing.lg }, + notFound: { color: colors.textMuted, fontSize: 15 }, + header: { height: 58, flexDirection: "row", alignItems: "center", borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border, paddingHorizontal: spacing.sm }, + backButton: { width: 44, height: 44, alignItems: "center", justifyContent: "center" }, + backLabel: { color: colors.blue, fontSize: 36, lineHeight: 38 }, + headerCopy: { flex: 1, alignItems: "center", gap: 2 }, + headerTitle: { color: colors.text, fontSize: 17, fontWeight: "700" }, + headerStatus: { color: colors.green, fontSize: 11 }, + transcript: { flex: 1 }, + transcriptContent: { paddingBottom: spacing.lg }, + loadingTranscript: { padding: spacing.xxl, alignItems: "center", gap: spacing.sm }, + emptyTranscriptTitle: { color: colors.text, fontSize: 15, fontWeight: "700" }, + loadingText: { color: colors.textMuted, fontSize: 13 }, + messageRow: { flexDirection: "row", gap: spacing.sm, paddingHorizontal: spacing.md, paddingVertical: spacing.md, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border }, + avatar: { width: 30, height: 30, borderRadius: 15, backgroundColor: colors.blue, alignItems: "center", justifyContent: "center" }, + agentAvatar: { backgroundColor: colors.green }, + avatarText: { color: "white", fontSize: 13, fontWeight: "800" }, + messageCopy: { flex: 1, gap: spacing.xs }, + role: { color: colors.textMuted, fontSize: 12, fontWeight: "700" }, + messageText: { color: colors.text, fontSize: 15, lineHeight: 22 }, + attentionPanel: { margin: spacing.md, padding: spacing.md, borderWidth: 1, borderColor: "#65521e", borderRadius: radius.md, backgroundColor: "#17160f", gap: spacing.sm }, + attentionHeadingRow: { flexDirection: "row", gap: spacing.sm, alignItems: "flex-start" }, + attentionGlyph: { width: 28, height: 28, borderRadius: 14, borderWidth: 2, borderColor: colors.amber, color: colors.amber, textAlign: "center", lineHeight: 24, fontWeight: "800" }, + attentionTitle: { color: colors.text, fontSize: 15, fontWeight: "700" }, + attentionSummary: { color: colors.textMuted, fontSize: 14, lineHeight: 20, marginTop: 3 }, + actionRow: { flexDirection: "row", justifyContent: "flex-end", gap: spacing.sm }, + optionList: { gap: spacing.xs }, + smallButton: { minHeight: 38, minWidth: 86, paddingHorizontal: spacing.md, borderRadius: radius.sm, backgroundColor: colors.blue, alignItems: "center", justifyContent: "center" }, + smallButtonLabel: { color: "white", fontSize: 14, fontWeight: "700" }, + denyButton: { backgroundColor: "transparent", borderWidth: 1, borderColor: "#6b3437" }, + denyLabel: { color: colors.red }, + pressed: { opacity: 0.75 }, + disabled: { opacity: 0.4 }, + questionInputRow: { flexDirection: "row", gap: spacing.xs, alignItems: "center" }, + questionInput: { flex: 1, minHeight: 40, borderRadius: radius.sm, borderWidth: 1, borderColor: colors.border, color: colors.text, paddingHorizontal: spacing.sm }, + readOnlyNote: { color: colors.amber, fontSize: 12, lineHeight: 17 }, + actionError: { color: colors.red, fontSize: 12 }, + composerError: { color: colors.red, fontSize: 12, paddingHorizontal: spacing.md, paddingVertical: spacing.xs }, + observerBanner: { color: colors.amber, fontSize: 12, textAlign: "center", backgroundColor: "#211c10", paddingVertical: spacing.xs }, + composer: { flexDirection: "row", alignItems: "flex-end", gap: spacing.sm, padding: spacing.sm, borderTopWidth: StyleSheet.hairlineWidth, borderTopColor: colors.border, backgroundColor: colors.background }, + composerInput: { flex: 1, maxHeight: 120, minHeight: 44, borderRadius: radius.md, borderWidth: 1, borderColor: colors.border, backgroundColor: colors.surface, color: colors.text, fontSize: 15, lineHeight: 20, paddingHorizontal: spacing.md, paddingVertical: spacing.sm }, + sendButton: { width: 44, height: 44, borderRadius: radius.md, backgroundColor: colors.blue, alignItems: "center", justifyContent: "center" }, + sendLabel: { color: "white", fontSize: 25, fontWeight: "800" }, +}); diff --git a/apps/companion/src/components/attention-row.tsx b/apps/companion/src/components/attention-row.tsx new file mode 100644 index 00000000..ef083678 --- /dev/null +++ b/apps/companion/src/components/attention-row.tsx @@ -0,0 +1,36 @@ +import { Pressable, StyleSheet, Text, View } from "react-native"; + +import type { CompanionAttentionItem } from "@/runtime/session-view"; +import { projectName, relativeTime } from "@/runtime/session-view"; +import { colors, spacing } from "@/theme"; + +export function AttentionRow({ attention, onPress }: { attention: CompanionAttentionItem; onPress: () => void }) { + return ( + [styles.row, pressed && styles.pressed]}> + {attention.item.kind === "question" ? "?" : "✓"} + + + {attention.title} + {relativeTime(new Date(attention.requestedAtMs).toISOString())} + + {attention.summary} + {projectName(attention.session)} + + + + ); +} + +const styles = StyleSheet.create({ + row: { minHeight: 86, flexDirection: "row", alignItems: "center", gap: spacing.sm, padding: spacing.md, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border }, + pressed: { backgroundColor: colors.surfaceRaised }, + badge: { width: 36, height: 36, borderRadius: 18, borderWidth: 2, borderColor: colors.amber, alignItems: "center", justifyContent: "center" }, + badgeText: { color: colors.amber, fontSize: 17, fontWeight: "800" }, + copy: { flex: 1, gap: 3 }, + topline: { flexDirection: "row", justifyContent: "space-between", gap: spacing.sm }, + title: { flex: 1, color: colors.text, fontSize: 15, fontWeight: "700" }, + summary: { color: colors.textMuted, fontSize: 14 }, + project: { color: colors.textDim, fontSize: 12 }, + time: { color: colors.amber, fontSize: 12 }, + chevron: { color: colors.textDim, fontSize: 24 }, +}); diff --git a/apps/companion/src/components/connection-pill.tsx b/apps/companion/src/components/connection-pill.tsx new file mode 100644 index 00000000..fc1ea2dc --- /dev/null +++ b/apps/companion/src/components/connection-pill.tsx @@ -0,0 +1,20 @@ +import { StyleSheet, Text, View } from "react-native"; + +import type { CompanionConnectionState } from "@/runtime/companion-runtime"; +import { colors, radius, spacing } from "@/theme"; + +export function ConnectionPill({ connection, label }: { connection: CompanionConnectionState; label?: string }) { + const connected = connection === "ready"; + return ( + + + {connected ? `Live · ${label ?? "T4"}` : "Reconnecting"} + + ); +} + +const styles = StyleSheet.create({ + pill: { maxWidth: 170, height: 30, flexDirection: "row", alignItems: "center", gap: spacing.xs, borderWidth: 1, borderColor: colors.border, borderRadius: radius.pill, paddingHorizontal: spacing.sm }, + dot: { width: 7, height: 7, borderRadius: 4 }, + label: { flexShrink: 1, color: colors.textMuted, fontSize: 12, fontWeight: "600" }, +}); diff --git a/apps/companion/src/components/session-row.tsx b/apps/companion/src/components/session-row.tsx new file mode 100644 index 00000000..682aadce --- /dev/null +++ b/apps/companion/src/components/session-row.tsx @@ -0,0 +1,36 @@ +import type { SessionRef } from "@t4-code/protocol"; +import { Pressable, StyleSheet, Text, View } from "react-native"; + +import { projectName, relativeTime } from "@/runtime/session-view"; +import { colors, spacing } from "@/theme"; + +export function SessionRow({ session, onPress }: { session: SessionRef; onPress: () => void }) { + const active = session.status === "active"; + return ( + [styles.row, pressed && styles.pressed]}> + + + + {projectName(session)} + {active ? "Running" : relativeTime(session.updatedAt)} + + {session.title} + + + + ); +} + +const styles = StyleSheet.create({ + row: { minHeight: 70, flexDirection: "row", alignItems: "center", gap: spacing.sm, paddingHorizontal: spacing.md, borderBottomWidth: StyleSheet.hairlineWidth, borderBottomColor: colors.border }, + pressed: { backgroundColor: colors.surfaceRaised }, + icon: { width: 34, height: 34, borderRadius: 8, borderWidth: 1, borderColor: colors.border, alignItems: "center", justifyContent: "center" }, + iconText: { color: colors.textMuted, fontSize: 19 }, + copy: { flex: 1, gap: 3 }, + topline: { flexDirection: "row", justifyContent: "space-between", gap: spacing.sm }, + project: { flex: 1, color: colors.text, fontSize: 15, fontWeight: "600" }, + title: { color: colors.textMuted, fontSize: 13 }, + status: { color: colors.textDim, fontSize: 12 }, + active: { color: colors.green }, + chevron: { color: colors.textDim, fontSize: 24 }, +}); diff --git a/apps/companion/src/components/transcript-speech-button.tsx b/apps/companion/src/components/transcript-speech-button.tsx new file mode 100644 index 00000000..e545caa3 --- /dev/null +++ b/apps/companion/src/components/transcript-speech-button.tsx @@ -0,0 +1,69 @@ +import * as Speech from "expo-speech"; +import { useEffect, useState } from "react"; +import { Pressable, StyleSheet, Text } from "react-native"; + +import { colors, radius, spacing } from "@/theme"; + +export function TranscriptSpeechButton({ + onError, + text, +}: { + readonly onError: (message: string) => void; + readonly text: string | null; +}) { + const [speaking, setSpeaking] = useState(false); + + useEffect(() => () => { + void Speech.stop(); + }, []); + + const toggle = async () => { + try { + if (speaking) { + await Speech.stop(); + setSpeaking(false); + return; + } + if (text === null) return; + setSpeaking(true); + Speech.speak(text.slice(0, Speech.maxSpeechInputLength), { + onDone: () => setSpeaking(false), + onStopped: () => setSpeaking(false), + onError: () => { + setSpeaking(false); + onError("Speech could not read this response."); + }, + }); + } catch (caught) { + setSpeaking(false); + onError(caught instanceof Error ? caught.message : "Speech is unavailable."); + } + }; + + return ( + void toggle()} + style={({ pressed }) => [styles.button, pressed && styles.pressed, text === null && !speaking && styles.disabled]} + > + {speaking ? "Stop" : "Listen"} + + ); +} + +const styles = StyleSheet.create({ + button: { + minHeight: 36, + minWidth: 64, + paddingHorizontal: spacing.sm, + borderRadius: radius.sm, + borderWidth: 1, + borderColor: colors.border, + alignItems: "center", + justifyContent: "center", + }, + label: { color: colors.text, fontSize: 12, fontWeight: "700" }, + pressed: { opacity: 0.75 }, + disabled: { opacity: 0.4 }, +}); diff --git a/apps/companion/src/components/voice-input-button.tsx b/apps/companion/src/components/voice-input-button.tsx new file mode 100644 index 00000000..71a1dc0d --- /dev/null +++ b/apps/companion/src/components/voice-input-button.tsx @@ -0,0 +1,151 @@ +import { + ExpoSpeechRecognitionModule, + useSpeechRecognitionEvent, +} from "expo-speech-recognition"; +import { useEffect, useRef, useState } from "react"; +import { Platform, Pressable, StyleSheet, Text } from "react-native"; + +import { reconcileVoiceTranscript, type VoiceTranscriptRange } from "@/runtime/voice-transcript"; +import { colors, radius, spacing } from "@/theme"; + +type VoiceInputState = "idle" | "starting" | "listening" | "processing"; + +function voiceErrorMessage(error: string, message: string): string { + if (error === "no-speech" || error === "speech-timeout") return "No speech was recognized."; + if (error === "not-allowed") return "Microphone or speech recognition permission was denied."; + if (error === "service-not-allowed" || error === "language-not-supported") { + return "Speech recognition is unavailable on this device."; + } + return message.trim() === "" ? "Speech recognition failed." : message; +} + +export function VoiceInputButton({ + disabled, + onChangeText, + onError, + value, +}: { + readonly disabled: boolean; + readonly onChangeText: (text: string) => void; + readonly onError: (message: string) => void; + readonly value: string; +}) { + const [state, setState] = useState("idle"); + const latestValue = useRef(value); + const startGeneration = useRef(0); + const transcriptRange = useRef(undefined); + latestValue.current = value; + + useSpeechRecognitionEvent("start", () => setState("listening")); + useSpeechRecognitionEvent("result", (event) => { + const update = reconcileVoiceTranscript( + latestValue.current, + event.results[0]?.transcript ?? "", + transcriptRange.current, + ); + if (update !== undefined) { + latestValue.current = update.value; + transcriptRange.current = update.range; + onChangeText(update.value); + } + if (event.isFinal) setState("processing"); + }); + useSpeechRecognitionEvent("error", (event) => { + setState("idle"); + if (event.error !== "aborted") onError(voiceErrorMessage(event.error, event.message)); + }); + useSpeechRecognitionEvent("nomatch", () => { + setState("idle"); + onError("No speech was recognized."); + }); + useSpeechRecognitionEvent("end", () => setState("idle")); + + useEffect( + () => () => { + startGeneration.current += 1; + ExpoSpeechRecognitionModule.abort(); + }, + [], + ); + + const toggle = async () => { + if (state !== "idle") { + startGeneration.current += 1; + if (state === "starting") { + setState("idle"); + ExpoSpeechRecognitionModule.abort(); + } else { + setState("processing"); + ExpoSpeechRecognitionModule.stop(); + } + return; + } + if (disabled) return; + if (Platform.OS === "web" || !ExpoSpeechRecognitionModule.isRecognitionAvailable()) { + onError("Speech recognition is unavailable on this device."); + return; + } + setState("starting"); + onError(""); + const generation = startGeneration.current + 1; + startGeneration.current = generation; + try { + const permission = await ExpoSpeechRecognitionModule.requestPermissionsAsync(); + if (startGeneration.current !== generation) return; + if (!permission.granted) { + setState("idle"); + onError("Microphone or speech recognition permission was denied."); + return; + } + transcriptRange.current = undefined; + ExpoSpeechRecognitionModule.start({ + lang: "en-US", + interimResults: true, + maxAlternatives: 1, + continuous: false, + requiresOnDeviceRecognition: ExpoSpeechRecognitionModule.supportsOnDeviceRecognition(), + }); + } catch (caught) { + if (startGeneration.current !== generation) return; + setState("idle"); + onError(caught instanceof Error ? caught.message : "Speech recognition failed."); + } + }; + + const active = state !== "idle"; + const label = state === "listening" ? "Stop" : state === "idle" ? "Voice" : "Wait"; + return ( + void toggle()} + style={({ pressed }) => [ + styles.button, + active && styles.active, + pressed && styles.pressed, + disabled && styles.disabled, + ]} + > + {label} + + ); +} + +const styles = StyleSheet.create({ + button: { + minHeight: 44, + minWidth: 58, + paddingHorizontal: spacing.sm, + borderRadius: radius.sm, + borderWidth: 1, + borderColor: colors.border, + alignItems: "center", + justifyContent: "center", + }, + active: { borderColor: colors.red, backgroundColor: "#251214" }, + label: { color: colors.textMuted, fontSize: 12, fontWeight: "700" }, + activeLabel: { color: colors.red }, + pressed: { opacity: 0.75 }, + disabled: { opacity: 0.4 }, +}); diff --git a/apps/companion/src/runtime/attention-notification-state.test.ts b/apps/companion/src/runtime/attention-notification-state.test.ts new file mode 100644 index 00000000..f150bd7b --- /dev/null +++ b/apps/companion/src/runtime/attention-notification-state.test.ts @@ -0,0 +1,23 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { AttentionNotificationState } from "./attention-notification-state"; + +describe("AttentionNotificationState", () => { + it("seeds the current inbox, emits only new keys, and allows resolved keys to return", () => { + const state = new AttentionNotificationState(); + + expect(state.update([{ key: "existing" }])).toEqual([]); + expect(state.update([{ key: "existing" }, { key: "new" }])).toEqual([{ key: "new" }]); + expect(state.update([{ key: "new" }])).toEqual([]); + expect(state.update([{ key: "existing" }, { key: "new" }])).toEqual([{ key: "existing" }]); + }); + + it("forgets one host when reset before seeding another", () => { + const state = new AttentionNotificationState(); + state.update([{ key: "host-a" }]); + state.reset(); + + expect(state.update([{ key: "host-b" }])).toEqual([]); + expect(state.update([{ key: "host-b" }, { key: "host-b-next" }])).toEqual([{ key: "host-b-next" }]); + }); +}); diff --git a/apps/companion/src/runtime/attention-notification-state.ts b/apps/companion/src/runtime/attention-notification-state.ts new file mode 100644 index 00000000..b0ecbd32 --- /dev/null +++ b/apps/companion/src/runtime/attention-notification-state.ts @@ -0,0 +1,24 @@ +export interface AttentionNotificationCandidate { + readonly key: string; +} + +/** Tracks one host's pending-attention keys without replaying its initial inbox. */ +export class AttentionNotificationState { + readonly #seen = new Set(); + #seeded = false; + + update(items: readonly T[]): readonly T[] { + const current = new Set(items.map((item) => item.key)); + const added = this.#seeded ? items.filter((item) => !this.#seen.has(item.key)) : []; + + this.#seen.clear(); + for (const key of current) this.#seen.add(key); + this.#seeded = true; + return added; + } + + reset(): void { + this.#seen.clear(); + this.#seeded = false; + } +} diff --git a/apps/companion/src/runtime/attention-notifications.tsx b/apps/companion/src/runtime/attention-notifications.tsx new file mode 100644 index 00000000..61df9e80 --- /dev/null +++ b/apps/companion/src/runtime/attention-notifications.tsx @@ -0,0 +1,161 @@ +import * as Notifications from "expo-notifications"; +import { useRouter } from "expo-router"; +import * as SecureStore from "expo-secure-store"; +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, + type PropsWithChildren, +} from "react"; +import { AppState, Platform } from "react-native"; + +import { AttentionNotificationState } from "./attention-notification-state"; +import { useCompanionRuntime } from "./companion-runtime"; +import { attentionFrom } from "./session-view"; + +const NOTIFICATIONS_STORAGE_KEY = "t4-companion.attention-notifications.v1"; +const ATTENTION_CHANNEL_ID = "attention"; + +export type AttentionNotificationStatus = "loading" | "disabled" | "enabled" | "denied" | "unavailable" | "error"; + +interface AttentionNotificationsValue { + readonly status: AttentionNotificationStatus; + readonly error: string | null; + readonly setEnabled: (enabled: boolean) => Promise; +} + +const AttentionNotificationsContext = createContext(null); + +Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldPlaySound: false, + shouldSetBadge: false, + shouldShowBanner: true, + shouldShowList: true, + }), +}); + +async function prepareNotifications(): Promise { + if (Platform.OS !== "android") return; + await Notifications.setNotificationChannelAsync(ATTENTION_CHANNEL_ID, { + name: "Agent attention", + importance: Notifications.AndroidImportance.HIGH, + vibrationPattern: [0, 250, 150, 250], + }); +} + +function notificationError(caught: unknown): string { + return caught instanceof Error && caught.message.trim() !== "" + ? caught.message + : "Notifications are unavailable on this device."; +} + +export function AttentionNotificationsProvider({ children }: PropsWithChildren) { + const runtime = useCompanionRuntime(); + const router = useRouter(); + const [status, setStatus] = useState("loading"); + const [error, setError] = useState(null); + const tracker = useRef(new AttentionNotificationState()); + const hostKey = runtime.host?.endpointKey ?? null; + const attention = useMemo(() => attentionFrom(runtime.projection), [runtime.projection]); + + useEffect(() => { + if (Platform.OS === "web") { + setStatus("unavailable"); + return; + } + let cancelled = false; + void SecureStore.getItemAsync(NOTIFICATIONS_STORAGE_KEY) + .then(async (stored) => { + if (cancelled) return; + if (stored !== "enabled") { + setStatus("disabled"); + return; + } + await prepareNotifications(); + const permission = await Notifications.getPermissionsAsync(); + if (!cancelled) setStatus(permission.granted ? "enabled" : "denied"); + }) + .catch((caught: unknown) => { + if (cancelled) return; + setError(notificationError(caught)); + setStatus("error"); + }); + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => { + tracker.current.reset(); + }, [hostKey]); + + useEffect(() => { + if (runtime.connection !== "ready") return; + const added = tracker.current.update(attention); + if (status !== "enabled" || AppState.currentState === "active" || added.length === 0) return; + + void Promise.all(added.map((item) => Notifications.scheduleNotificationAsync({ content: { + title: "T4 needs your attention", + body: "Open T4 Companion to review it.", + data: { sessionId: String(item.session.sessionId) }, + }, trigger: null }))).catch((caught: unknown) => { + setError(notificationError(caught)); + setStatus("error"); + }); + }, [attention, runtime.connection, status]); + + useEffect(() => { + const subscription = Notifications.addNotificationResponseReceivedListener((response) => { + const sessionId = response.notification.request.content.data?.sessionId; + if (typeof sessionId === "string" && sessionId !== "") { + router.push({ pathname: "/session/[id]", params: { id: sessionId } }); + } + }); + return () => subscription.remove(); + }, [router]); + + const setEnabled = useCallback(async (enabled: boolean): Promise => { + setError(null); + if (!enabled) { + await SecureStore.deleteItemAsync(NOTIFICATIONS_STORAGE_KEY); + setStatus("disabled"); + return true; + } + if (Platform.OS === "web") { + setStatus("unavailable"); + return false; + } + try { + await prepareNotifications(); + const current = await Notifications.getPermissionsAsync(); + const permission = current.granted ? current : await Notifications.requestPermissionsAsync(); + if (!permission.granted) { + setStatus("denied"); + return false; + } + await SecureStore.setItemAsync(NOTIFICATIONS_STORAGE_KEY, "enabled", { + keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY, + }); + setStatus("enabled"); + return true; + } catch (caught) { + setError(notificationError(caught)); + setStatus("error"); + return false; + } + }, []); + + const value = useMemo(() => ({ status, error, setEnabled }), [status, error, setEnabled]); + return {children}; +} + +export function useAttentionNotifications(): AttentionNotificationsValue { + const value = useContext(AttentionNotificationsContext); + if (value === null) throw new Error("useAttentionNotifications must be used inside AttentionNotificationsProvider"); + return value; +} diff --git a/apps/companion/src/runtime/backend.test.ts b/apps/companion/src/runtime/backend.test.ts new file mode 100644 index 00000000..a7d037f0 --- /dev/null +++ b/apps/companion/src/runtime/backend.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { parseCompanionHost } from "./backend"; + +describe("parseCompanionHost", () => { + it("creates the default Tailnet WebSocket route", () => { + const host = parseCompanionHost("mac.example.ts.net:8445"); + expect(host.origin).toBe("https://mac.example.ts.net:8445"); + expect(host.wsUrl).toBe("wss://mac.example.ts.net:8445/v1/ws"); + expect(host.profileId).toBe("default"); + }); + + it("creates a named-profile route and preserves the device identity", () => { + const host = parseCompanionHost( + "https://devbox.example.ts.net:8445", + "nightly", + { + endpointKey: "https://devbox.example.ts.net:8445#profile=nightly", + deviceId: "device-a", + deviceToken: "token-a", + }, + ); + expect(host.wsUrl).toBe("wss://devbox.example.ts.net:8445/v1/profiles/nightly/ws"); + expect(host.deviceId).toBe("device-a"); + expect(host.deviceToken).toBe("token-a"); + }); + + it("does not send one host's credential to another host or profile", () => { + const existing = { + endpointKey: "https://devbox.example.ts.net:8445#profile=default", + deviceId: "device-a", + deviceToken: "token-a", + }; + const otherHost = parseCompanionHost("https://other.example.ts.net:8445", "default", existing); + const otherProfile = parseCompanionHost("https://devbox.example.ts.net:8445", "nightly", existing); + + expect(otherHost.deviceId).toBe("device-a"); + expect(otherHost.deviceToken).toBeUndefined(); + expect(otherProfile.deviceToken).toBeUndefined(); + }); + + it("rejects public, insecure, and path-bearing addresses", () => { + expect(() => parseCompanionHost("http://devbox.example.ts.net:8445")).toThrow(/HTTPS/); + expect(() => parseCompanionHost("https://example.com")).toThrow(/Tailscale/); + expect(() => parseCompanionHost("https://devbox.example.ts.net:8445/v1/ws")).toThrow(/host address only/); + }); +}); diff --git a/apps/companion/src/runtime/backend.ts b/apps/companion/src/runtime/backend.ts new file mode 100644 index 00000000..029fc424 --- /dev/null +++ b/apps/companion/src/runtime/backend.ts @@ -0,0 +1,80 @@ +export const DEFAULT_PROFILE_ID = "default"; + +const MAX_URL_LENGTH = 2_048; +const MAX_PROFILE_ID_LENGTH = 64; + +export interface CompanionHost { + readonly version: 1; + readonly endpointKey: string; + readonly origin: string; + readonly profileId: string; + readonly wsUrl: string; + readonly label: string; + readonly deviceId: string; + readonly deviceToken?: string; +} + +function normalizeProfileId(value?: string): string { + const profile = value?.trim() || DEFAULT_PROFILE_ID; + if ( + profile.length > MAX_PROFILE_ID_LENGTH || + !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(profile) + ) { + throw new Error("Use letters, numbers, dots, dashes, or underscores for the profile ID."); + } + return profile; +} + +function deviceId(): string { + const random = Math.random().toString(36).slice(2, 12); + return `t4-companion-${Date.now().toString(36)}-${random}`; +} + +export function parseCompanionHost( + value: string, + profileValue?: string, + existing?: Pick, +): CompanionHost { + const trimmed = value.trim(); + if (trimmed.length === 0) throw new Error("Enter the HTTPS address shown by T4 Code on your computer."); + if (trimmed.length > MAX_URL_LENGTH) throw new Error("That address is too long."); + + const candidate = trimmed.includes("://") ? trimmed : `https://${trimmed}`; + let url: URL; + try { + url = new URL(candidate); + } catch { + throw new Error("Enter a valid HTTPS Tailnet address."); + } + if (url.protocol !== "https:") throw new Error("Use the HTTPS address, not HTTP."); + if (url.username !== "" || url.password !== "") throw new Error("The address cannot include credentials."); + if (url.pathname !== "/" || url.search !== "" || url.hash !== "") { + throw new Error("Enter the host address only, without a path, query, or fragment."); + } + const hostname = url.hostname.toLowerCase(); + if (hostname === "ts.net" || !hostname.endsWith(".ts.net")) { + throw new Error("Use the full Tailscale hostname ending in .ts.net."); + } + + const profileId = normalizeProfileId(profileValue); + const endpointKey = `${url.origin}#profile=${profileId}`; + const websocket = new URL(url.origin); + websocket.protocol = "wss:"; + websocket.pathname = + profileId === DEFAULT_PROFILE_ID + ? "/v1/ws" + : `/v1/profiles/${encodeURIComponent(profileId)}/ws`; + + return Object.freeze({ + version: 1, + endpointKey, + origin: url.origin, + profileId, + wsUrl: websocket.toString(), + label: `T4 on ${hostname.slice(0, hostname.indexOf("."))}`, + deviceId: existing?.deviceId ?? deviceId(), + ...(existing?.endpointKey !== endpointKey || existing.deviceToken === undefined + ? {} + : { deviceToken: existing.deviceToken }), + }); +} diff --git a/apps/companion/src/runtime/companion-runtime.tsx b/apps/companion/src/runtime/companion-runtime.tsx new file mode 100644 index 00000000..e244742a --- /dev/null +++ b/apps/companion/src/runtime/companion-runtime.tsx @@ -0,0 +1,366 @@ +import { + createOmpClient, + createProjectionStore, + isConfirmationDecisionConsumed, + type OmpClient, + type OmpClientError, + type OmpClientState, + type ProjectionSnapshot, + type Unsubscribe, +} from "@t4-code/client"; +import { + ADDITIVE_FEATURES, + DEVICE_CAPABILITIES, + type PendingAttentionItem, + type SessionRef, +} from "@t4-code/protocol"; +import * as SecureStore from "expo-secure-store"; +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, + type PropsWithChildren, +} from "react"; +import { AppState, Platform } from "react-native"; + +import { parseCompanionHost, type CompanionHost } from "./backend"; +import { NativeWebSocketTransport } from "./native-websocket-transport"; +import { applySessionListInventory } from "./session-inventory"; +import { canWriteSession, warmSession } from "./session-view"; + +// Secure Store keys are intentionally portable across iOS and Android. iOS +// rejects colons even though browser storage commonly accepts them. +const HOST_STORAGE_KEY = "t4-companion.host.v1"; +const COMPATIBILITY_FEATURES = ADDITIVE_FEATURES.filter( + (feature) => feature !== "prompt.images" && feature !== "transcript.images", +); + +export type CompanionConnectionState = "loading" | OmpClientState; + +interface RuntimeValue { + readonly host: CompanionHost | null; + readonly connection: CompanionConnectionState; + readonly error: string | null; + readonly hostId: string | null; + readonly projection: ProjectionSnapshot; + readonly configureHost: (address: string, profileId?: string) => Promise; + readonly forgetHost: () => Promise; + readonly retry: () => void; + readonly pair: (code: string) => Promise; + readonly refresh: () => Promise; + readonly openSession: (session: SessionRef) => Promise; + readonly sendMessage: (session: SessionRef, message: string) => Promise; + readonly respond: (session: SessionRef, item: PendingAttentionItem, value?: string) => Promise; + readonly decideConfirmation: (input: { + session: SessionRef; + confirmationId: string; + commandId: string; + decision: "approve" | "deny"; + }) => Promise; +} + +const initialProjection = createProjectionStore().snapshot; +const RuntimeContext = createContext(null); + +async function loadStoredHost(): Promise { + const raw = await SecureStore.getItemAsync(HOST_STORAGE_KEY); + if (raw === null) return null; + const parsed = JSON.parse(raw) as Partial; + if ( + parsed.version !== 1 || + typeof parsed.endpointKey !== "string" || + typeof parsed.origin !== "string" || + typeof parsed.profileId !== "string" || + typeof parsed.deviceId !== "string" + ) { + throw new Error("The saved host is damaged. Add it again."); + } + return parseCompanionHost(parsed.origin, parsed.profileId, { + endpointKey: parsed.endpointKey, + deviceId: parsed.deviceId, + ...(typeof parsed.deviceToken === "string" ? { deviceToken: parsed.deviceToken } : {}), + }); +} + +async function saveStoredHost(host: CompanionHost): Promise { + await SecureStore.setItemAsync(HOST_STORAGE_KEY, JSON.stringify(host), { + keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY, + }); +} + +function publicError(error: unknown): string { + if (error instanceof Error && error.message.trim() !== "") return error.message; + return "T4 could not complete that action."; +} + +export function CompanionRuntimeProvider({ children }: PropsWithChildren) { + const [host, setHost] = useState(null); + const [connection, setConnection] = useState("loading"); + const [error, setError] = useState(null); + const [hostId, setHostId] = useState(null); + const [projection, setProjection] = useState(initialProjection); + const clientRef = useRef(null); + const projectionRef = useRef(createProjectionStore({ maxWarmSessions: 12 })); + const liveHostRef = useRef(null); + const liveHostIdRef = useRef(null); + const refreshGenerationRef = useRef(-1); + + const refreshWith = useCallback(async (client: OmpClient, currentHostId: string) => { + if (client.state !== "ready") return; + const response = await client.command({ hostId: currentHostId, command: "session.list", args: {} }); + if (!response.ok) throw new Error(response.error?.message ?? "T4 could not load sessions."); + const cursor = applySessionListInventory(projectionRef.current, currentHostId, response.result); + await client.command({ hostId: currentHostId, command: "host.watch", args: { cursor } }); + }, []); + + useEffect(() => { + let cancelled = false; + void loadStoredHost() + .then((stored) => { + if (cancelled) return; + liveHostRef.current = stored; + setHost(stored); + setConnection(stored === null ? "idle" : "connecting"); + }) + .catch((caught: unknown) => { + if (cancelled) return; + setError(publicError(caught)); + setConnection("idle"); + }); + return () => { + cancelled = true; + }; + }, []); + + useEffect(() => projectionRef.current.subscribe(setProjection), []); + + useEffect(() => { + if (host === null) return; + let disposed = false; + const unsubscribes: Unsubscribe[] = []; + const transportFactory = async () => { + const transport = new NativeWebSocketTransport(host.wsUrl); + await transport.open(); + return transport; + }; + const client = createOmpClient({ + transport: transportFactory, + projection: projectionRef.current, + capabilities: DEVICE_CAPABILITIES, + requestedFeatures: ADDITIVE_FEATURES, + compatibilityRequestedFeatures: COMPATIBILITY_FEATURES, + authentication: () => + liveHostRef.current?.deviceToken === undefined + ? undefined + : { + deviceId: liveHostRef.current.deviceId, + deviceToken: liveHostRef.current.deviceToken, + }, + privilegedPairResult: async (result) => { + const current = liveHostRef.current; + if (current === null || current.endpointKey !== host.endpointKey) { + throw new Error("The selected host changed during pairing."); + } + const paired = Object.freeze({ ...current, deviceId: result.deviceId, deviceToken: result.deviceToken }); + await saveStoredHost(paired); + liveHostRef.current = paired; + setHost(paired); + }, + client: { + name: "T4 Companion", + version: "0.1.29", + build: "expo-57", + platform: Platform.OS, + }, + reconnect: { baseMs: 300, maxMs: 10_000 }, + }); + clientRef.current = client; + refreshGenerationRef.current = -1; + unsubscribes.push( + client.onEvent((event) => { + if (event.kind !== "welcome") return; + const nextHostId = String(event.payload.hostId); + liveHostIdRef.current = nextHostId; + setHostId(nextHostId); + if (event.payload.authentication !== "pairing-required" && client.state === "ready") { + refreshGenerationRef.current = client.snapshot().generation; + void refreshWith(client, nextHostId).catch((caught: unknown) => setError(publicError(caught))); + } + }), + client.onState((snapshot) => { + if (disposed) return; + setConnection(snapshot.state); + if (snapshot.state === "ready") setError(null); + if ( + snapshot.state === "ready" && + liveHostIdRef.current !== null && + refreshGenerationRef.current !== snapshot.generation + ) { + refreshGenerationRef.current = snapshot.generation; + void refreshWith(client, liveHostIdRef.current).catch((caught: unknown) => setError(publicError(caught))); + } + }), + client.onError((caught: OmpClientError) => { + if (!disposed) setError(caught.message); + }), + ); + void client.connect().catch((caught: unknown) => { + if (!disposed) setError(publicError(caught)); + }); + const appState = AppState.addEventListener("change", (state) => { + if (state === "active") client.wake(); + }); + return () => { + disposed = true; + appState.remove(); + for (const unsubscribe of unsubscribes) unsubscribe(); + if (clientRef.current === client) clientRef.current = null; + void client.close(); + }; + }, [host, refreshWith]); + + const configureHost = useCallback(async (address: string, profileId?: string) => { + const next = parseCompanionHost(address, profileId, liveHostRef.current ?? undefined); + await saveStoredHost(next); + liveHostRef.current = next; + liveHostIdRef.current = null; + setHostId(null); + setError(null); + setConnection("connecting"); + setHost(next); + }, []); + + const forgetHost = useCallback(async () => { + await SecureStore.deleteItemAsync(HOST_STORAGE_KEY); + liveHostRef.current = null; + liveHostIdRef.current = null; + setHost(null); + setHostId(null); + setError(null); + setConnection("idle"); + }, []); + + const retry = useCallback(() => { + setError(null); + clientRef.current?.reconnectNow(); + }, []); + + const refresh = useCallback(async () => { + const client = clientRef.current; + const currentHostId = liveHostIdRef.current; + if (client === null || currentHostId === null) throw new Error("T4 is not connected yet."); + await refreshWith(client, currentHostId); + }, [refreshWith]); + + const pair = useCallback(async (code: string) => { + const client = clientRef.current; + const current = liveHostRef.current; + if (client === null || current === null) throw new Error("T4 is not ready to pair."); + await client.pairStart({ + code: code.trim(), + deviceId: current.deviceId, + deviceName: Platform.OS === "ios" ? "T4 Companion on iPhone" : "T4 Companion on Android", + platform: Platform.OS, + requestedCapabilities: DEVICE_CAPABILITIES, + }); + }, []); + + const openSession = useCallback(async (session: SessionRef) => { + const client = clientRef.current; + if (client === null || client.state !== "ready") throw new Error("T4 is reconnecting."); + projectionRef.current.activateSession(String(session.hostId), String(session.sessionId)); + const response = await client.attach(String(session.hostId), String(session.sessionId)); + if (!response.ok) throw new Error(response.error?.message ?? "T4 could not open that session."); + }, []); + + const sendMessage = useCallback(async (session: SessionRef, message: string) => { + const client = clientRef.current; + const text = message.trim(); + if (text === "") return; + if (client === null || client.state !== "ready") throw new Error("T4 is reconnecting."); + const attached = warmSession( + projectionRef.current.snapshot, + String(session.hostId), + String(session.sessionId), + ); + if (!canWriteSession(session, attached !== undefined)) + throw new Error("This session is active in another app and is read-only here."); + const command = session.status === "active" ? "session.steer" : "session.prompt"; + const response = await client.command({ + hostId: String(session.hostId), + sessionId: String(session.sessionId), + command, + expectedRevision: String(session.revision), + args: { message: text }, + }); + if (!response.ok) throw new Error(response.error?.message ?? "T4 did not accept that message."); + }, []); + + const respond = useCallback(async (session: SessionRef, item: PendingAttentionItem, value?: string) => { + const client = clientRef.current; + if (client === null || client.state !== "ready") throw new Error("T4 is reconnecting."); + const attached = warmSession( + projectionRef.current.snapshot, + String(session.hostId), + String(session.sessionId), + ); + if (!canWriteSession(session, attached !== undefined)) + throw new Error("This session is active in another app and is read-only here."); + const args: Record = { requestId: item.id }; + if (item.kind === "question") args.value = value?.trim() ?? ""; + else args.confirmed = value !== "deny"; + const response = await client.command({ + hostId: String(session.hostId), + sessionId: String(session.sessionId), + command: "session.ui.respond", + expectedRevision: String(session.revision), + args, + }); + if (!response.ok) throw new Error(response.error?.message ?? "T4 did not accept that response."); + }, []); + + const decideConfirmation = useCallback(async ({ session, ...decision }: { + session: SessionRef; + confirmationId: string; + commandId: string; + decision: "approve" | "deny"; + }) => { + const client = clientRef.current; + if (client === null || client.state !== "ready") throw new Error("T4 is reconnecting."); + const response = await client.confirm({ + ...decision, + hostId: String(session.hostId), + sessionId: String(session.sessionId), + }); + if (!isConfirmationDecisionConsumed(response)) throw new Error("That confirmation is no longer current."); + }, []); + + const value = useMemo(() => ({ + host, + connection, + error, + hostId, + projection, + configureHost, + forgetHost, + retry, + pair, + refresh, + openSession, + sendMessage, + respond, + decideConfirmation, + }), [host, connection, error, hostId, projection, configureHost, forgetHost, retry, pair, refresh, openSession, sendMessage, respond, decideConfirmation]); + + return {children}; +} + +export function useCompanionRuntime(): RuntimeValue { + const value = useContext(RuntimeContext); + if (value === null) throw new Error("useCompanionRuntime must be used inside CompanionRuntimeProvider"); + return value; +} diff --git a/apps/companion/src/runtime/native-websocket-origin.ts b/apps/companion/src/runtime/native-websocket-origin.ts new file mode 100644 index 00000000..e0c8593f --- /dev/null +++ b/apps/companion/src/runtime/native-websocket-origin.ts @@ -0,0 +1,5 @@ +export function nativeWebSocketOrigin(value: string): string { + const url = new URL(value); + if (url.protocol !== "wss:") throw new Error("native T4 connections require WSS"); + return `https://${url.host}`; +} diff --git a/apps/companion/src/runtime/native-websocket-transport.test.ts b/apps/companion/src/runtime/native-websocket-transport.test.ts new file mode 100644 index 00000000..14055138 --- /dev/null +++ b/apps/companion/src/runtime/native-websocket-transport.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { nativeWebSocketOrigin } from "./native-websocket-origin"; + +describe("nativeWebSocketOrigin", () => { + it("maps the secure socket endpoint to its exact HTTPS Origin", () => { + expect(nativeWebSocketOrigin("wss://workstation.example-tailnet.ts.net:8445/v1/ws")).toBe( + "https://workstation.example-tailnet.ts.net:8445", + ); + }); + + it("rejects plaintext native transports", () => { + expect(() => nativeWebSocketOrigin("ws://workstation.example-tailnet.ts.net:8445/v1/ws")).toThrow( + "native T4 connections require WSS", + ); + }); +}); diff --git a/apps/companion/src/runtime/native-websocket-transport.ts b/apps/companion/src/runtime/native-websocket-transport.ts new file mode 100644 index 00000000..780e3b65 --- /dev/null +++ b/apps/companion/src/runtime/native-websocket-transport.ts @@ -0,0 +1,112 @@ +import { Platform } from "react-native"; + +import type { OmpTransport, Unsubscribe } from "@t4-code/client"; +import { nativeWebSocketOrigin } from "./native-websocket-origin"; + +const MAX_MESSAGE_BYTES = 4 * 1024 * 1024; +const OPEN_TIMEOUT_MS = 12_000; + +type NativeWebSocketConstructor = new ( + url: string, + protocols: string[], + options: { headers: Record }, +) => WebSocket; + +function byteLength(value: string): number { + return new TextEncoder().encode(value).byteLength; +} + +export class NativeWebSocketTransport implements OmpTransport { + private socket: WebSocket | undefined; + private readonly messages = new Set<(data: string | Uint8Array) => void>(); + private readonly closes = new Set<(code?: number, reason?: string) => void>(); + private readonly errors = new Set<(error: unknown) => void>(); + + constructor(private readonly url: string) {} + + open(): Promise { + if (this.socket !== undefined) return Promise.reject(new Error("connection already started")); + return new Promise((resolve, reject) => { + let settled = false; + const socket = + Platform.OS === "web" + ? new WebSocket(this.url) + : new (WebSocket as unknown as NativeWebSocketConstructor)(this.url, [], { + headers: { origin: nativeWebSocketOrigin(this.url) }, + }); + socket.binaryType = "arraybuffer"; + this.socket = socket; + const timer = setTimeout(() => { + if (settled) return; + settled = true; + reject(new Error("T4 did not answer in time.")); + socket.close(); + }, OPEN_TIMEOUT_MS); + socket.onopen = () => { + if (settled) return; + settled = true; + clearTimeout(timer); + resolve(); + }; + socket.onmessage = (event) => { + const data = event.data; + if (typeof data === "string") { + if (byteLength(data) > MAX_MESSAGE_BYTES) return this.fail("T4 sent a message that was too large."); + for (const listener of this.messages) listener(data); + } else if (data instanceof ArrayBuffer) { + if (data.byteLength > MAX_MESSAGE_BYTES) return this.fail("T4 sent a message that was too large."); + for (const listener of this.messages) listener(new Uint8Array(data)); + } + }; + socket.onerror = () => { + if (!settled) { + settled = true; + clearTimeout(timer); + reject(new Error("The secure connection to T4 failed.")); + } + this.fail("The secure connection to T4 failed."); + }; + socket.onclose = (event) => { + clearTimeout(timer); + this.socket = undefined; + if (!settled) { + settled = true; + reject(new Error("T4 closed the connection before it was ready.")); + } + for (const listener of this.closes) listener(event.code, event.reason.slice(0, 256)); + }; + }); + } + + send(data: string): void { + if (byteLength(data) > MAX_MESSAGE_BYTES) throw new Error("outgoing message is too large"); + if (this.socket?.readyState !== WebSocket.OPEN) throw new Error("T4 is not connected"); + this.socket.send(data); + } + + close(): void { + const socket = this.socket; + this.socket = undefined; + if (socket !== undefined) socket.close(1000, "client closed"); + } + + onMessage(listener: (data: string | Uint8Array) => void): Unsubscribe { + this.messages.add(listener); + return () => this.messages.delete(listener); + } + + onClose(listener: (code?: number, reason?: string) => void): Unsubscribe { + this.closes.add(listener); + return () => this.closes.delete(listener); + } + + onError(listener: (error: unknown) => void): Unsubscribe { + this.errors.add(listener); + return () => this.errors.delete(listener); + } + + private fail(message: string): void { + const error = new Error(message); + for (const listener of this.errors) listener(error); + } +} diff --git a/apps/companion/src/runtime/session-inventory.test.ts b/apps/companion/src/runtime/session-inventory.test.ts new file mode 100644 index 00000000..9f9bc28b --- /dev/null +++ b/apps/companion/src/runtime/session-inventory.test.ts @@ -0,0 +1,52 @@ +import { createProjectionStore } from "@t4-code/client"; +import { + hostId, + projectId, + revision, + sessionId, + type SessionRef, +} from "@t4-code/protocol"; +import { describe, expect, it } from "vite-plus/test"; + +import { applySessionListInventory } from "./session-inventory"; + +function session(id: string): SessionRef { + return { + hostId: hostId("host-live"), + sessionId: sessionId(id), + project: { projectId: projectId("project-t4"), name: "T4 Code" }, + revision: revision(`revision-${id}`), + title: `Session ${id}`, + status: "active", + updatedAt: "2026-07-19T12:00:00.000Z", + }; +} + +describe("companion session inventory", () => { + it("shows the authoritative session.list rows and removes rows missing from the next complete list", () => { + const projection = createProjectionStore(); + const firstCursor = applySessionListInventory(projection, "host-live", { + cursor: { epoch: "epoch-live", seq: 3 }, + sessions: [session("one"), session("two")], + totalCount: 2, + truncated: false, + }); + + expect(firstCursor).toEqual({ epoch: "epoch-live", seq: 3 }); + expect([...projection.snapshot.sessionIndex.values()].map((item) => item.title)).toEqual([ + "Session one", + "Session two", + ]); + + applySessionListInventory(projection, "host-live", { + cursor: { epoch: "epoch-live", seq: 4 }, + sessions: [session("two")], + totalCount: 1, + truncated: false, + }); + + expect([...projection.snapshot.sessionIndex.values()].map((item) => item.title)).toEqual([ + "Session two", + ]); + }); +}); diff --git a/apps/companion/src/runtime/session-inventory.ts b/apps/companion/src/runtime/session-inventory.ts new file mode 100644 index 00000000..88cda7b4 --- /dev/null +++ b/apps/companion/src/runtime/session-inventory.ts @@ -0,0 +1,30 @@ +import type { ProjectionStore } from "@t4-code/client"; +import { + PROTOCOL_VERSION, + decodeSessionListResult, + decodeSessions, + type Cursor, +} from "@t4-code/protocol"; + +/** + * Materialize the authoritative session.list response in the same shared + * projection used by live session.delta events. + */ +export function applySessionListInventory( + projection: ProjectionStore, + currentHostId: string, + input: unknown, +): Cursor { + const list = decodeSessionListResult(input); + const frame = decodeSessions({ + v: PROTOCOL_VERSION, + type: "sessions", + hostId: currentHostId, + cursor: list.cursor, + sessions: list.sessions, + totalCount: list.totalCount, + truncated: list.truncated, + }); + projection.applyPublicFrame(frame); + return list.cursor; +} diff --git a/apps/companion/src/runtime/session-view.test.ts b/apps/companion/src/runtime/session-view.test.ts new file mode 100644 index 00000000..f9d5e1f2 --- /dev/null +++ b/apps/companion/src/runtime/session-view.test.ts @@ -0,0 +1,88 @@ +import type { ProjectionSnapshot } from "@t4-code/client"; +import { createProjectionSnapshot } from "@t4-code/client"; +import type { SessionRef } from "@t4-code/protocol"; +import { hostId, projectId, revision, sessionId } from "@t4-code/protocol"; +import { describe, expect, it } from "vite-plus/test"; + +import { + attentionFrom, + canWriteSession, + latestAssistantText, + relativeTime, + transcriptDisplayState, +} from "./session-view"; + +function ref(overrides: Partial = {}): SessionRef { + return { + hostId: hostId("host-a"), + sessionId: sessionId("session-a"), + project: { projectId: projectId("project-a"), name: "T4 Code" }, + revision: revision("revision-a"), + title: "Build the companion", + status: "active", + updatedAt: "2026-07-19T12:00:00.000Z", + ...overrides, + }; +} + +describe("companion session view", () => { + it("sorts oldest attention first", () => { + const first = ref({ + attention: { + pending: [{ kind: "approval", id: "a", title: "Approve", summary: "Run tests", requestedAt: "2026-07-19T12:00:00.000Z" }], + pendingCount: 1, + truncated: false, + }, + }); + const second = ref({ + sessionId: sessionId("session-b"), + attention: { + pending: [{ kind: "question", id: "b", question: "Which branch?", options: [], allowText: true, requestedAt: "2026-07-19T12:05:00.000Z" }], + pendingCount: 1, + truncated: false, + }, + }); + const base = createProjectionSnapshot(); + const snapshot = { + ...base, + sessionIndex: new Map([ + ["host-a\u0000session-b", second], + ["host-a\u0000session-a", first], + ]), + } as ProjectionSnapshot; + expect(attentionFrom(snapshot).map((item) => item.item.id)).toEqual(["a", "b"]); + }); + + it("refuses writes while another app controls the session", () => { + expect(canWriteSession(ref())).toBe(false); + expect(canWriteSession(ref(), true)).toBe(true); + expect(canWriteSession(ref({ status: "idle" }))).toBe(true); + expect(canWriteSession(ref({ + liveState: { sessionControl: { mode: "observer", lockStatus: "live", transcript: "live" } }, + }), true)).toBe(false); + }); + + it("selects the newest assistant text for speech", () => { + expect(latestAssistantText([ + { data: { role: "assistant", text: "first" } }, + { data: { role: "user", text: "question" } }, + { data: { role: "assistant", message: "latest" } }, + { data: { role: "assistant", text: " " } }, + ])).toBe("latest"); + expect(latestAssistantText([{ data: { role: "user", text: "only user text" } }])).toBeNull(); + }); + + it("formats compact relative times", () => { + const now = Date.parse("2026-07-19T13:00:00.000Z"); + expect(relativeTime("2026-07-19T12:59:45.000Z", now)).toBe("now"); + expect(relativeTime("2026-07-19T12:42:00.000Z", now)).toBe("18m"); + expect(relativeTime("2026-07-17T13:00:00.000Z", now)).toBe("2d"); + }); + + it("stops calling an empty completed transcript a loading transcript", () => { + expect(transcriptDisplayState(false, 0)).toBe("loading"); + expect(transcriptDisplayState(true, 0)).toBe("empty"); + expect(transcriptDisplayState(false, 1)).toBe("ready"); + expect(transcriptDisplayState(true, 1)).toBe("ready"); + }); +}); diff --git a/apps/companion/src/runtime/session-view.ts b/apps/companion/src/runtime/session-view.ts new file mode 100644 index 00000000..9f239bde --- /dev/null +++ b/apps/companion/src/runtime/session-view.ts @@ -0,0 +1,105 @@ +import type { PendingAttentionItem, SessionRef } from "@t4-code/protocol"; +import type { ProjectionSnapshot, SessionProjection } from "@t4-code/client"; + +export interface CompanionAttentionItem { + readonly key: string; + readonly session: SessionRef; + readonly item: PendingAttentionItem; + readonly title: string; + readonly summary: string; + readonly requestedAtMs: number; +} + +export interface TranscriptTextEntry { + readonly data: Readonly>; +} + +export function sessionKey(hostId: string, sessionId: string): string { + return `${hostId}\u0000${sessionId}`; +} + +export function sessionsFrom(snapshot: ProjectionSnapshot): readonly SessionRef[] { + return [...snapshot.sessionIndex.values()].sort( + (left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt), + ); +} + +export function attentionFrom(snapshot: ProjectionSnapshot): readonly CompanionAttentionItem[] { + const result: CompanionAttentionItem[] = []; + for (const session of snapshot.sessionIndex.values()) { + for (const item of session.attention?.pending ?? []) { + result.push({ + key: `${String(session.hostId)}:${String(session.sessionId)}:${item.kind}:${item.id}`, + session, + item, + title: item.kind === "question" ? "Agent question" : item.kind === "plan" ? "Plan ready" : item.title, + summary: item.kind === "question" ? item.question : item.summary, + requestedAtMs: Date.parse(item.requestedAt), + }); + } + } + return result.sort((left, right) => left.requestedAtMs - right.requestedAtMs); +} + +export function warmSession( + snapshot: ProjectionSnapshot, + hostId: string, + sessionId: string, +): SessionProjection | undefined { + return snapshot.sessions.get(sessionKey(hostId, sessionId)); +} + +export function canWriteSession(session: SessionRef, attached = false): boolean { + if (session.liveState?.sessionControl !== undefined) return false; + // A live CLI session is read-only until session.attach has warmed its + // transcript and the host has confirmed whether another process owns it. + return session.status !== "active" || attached; +} + +export function projectName(session: SessionRef): string { + return session.project.name ?? String(session.project.projectId); +} + +export function relativeTime(timestamp: string, now = Date.now()): string { + const elapsed = Math.max(0, now - Date.parse(timestamp)); + const minutes = Math.floor(elapsed / 60_000); + if (minutes < 1) return "now"; + if (minutes < 60) return `${minutes}m`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h`; + const days = Math.floor(hours / 24); + return `${days}d`; +} + +export function entryText(data: Readonly>): string | null { + const text = data.text; + if (typeof text === "string" && text.trim() !== "") return text; + const message = data.message; + if (typeof message === "string" && message.trim() !== "") return message; + return null; +} + +export function entryRole(data: Readonly>): "You" | "Agent" | "Update" { + if (data.role === "user") return "You"; + if (data.role === "assistant") return "Agent"; + return "Update"; +} + +export function latestAssistantText(entries: readonly TranscriptTextEntry[]): string | null { + for (let index = entries.length - 1; index >= 0; index -= 1) { + const entry = entries[index]; + if (entry !== undefined && entryRole(entry.data) === "Agent") { + const text = entryText(entry.data); + if (text !== null) return text; + } + } + return null; +} + +export function transcriptDisplayState( + finishedOpening: boolean, + visibleEntryCount: number, +): "loading" | "empty" | "ready" { + if (visibleEntryCount > 0) return "ready"; + return finishedOpening ? "empty" : "loading"; +} diff --git a/apps/companion/src/runtime/voice-transcript.test.ts b/apps/companion/src/runtime/voice-transcript.test.ts new file mode 100644 index 00000000..e8dc38de --- /dev/null +++ b/apps/companion/src/runtime/voice-transcript.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { reconcileVoiceTranscript } from "./voice-transcript"; + +describe("reconcileVoiceTranscript", () => { + it("replaces interim recognition while preserving user text entered after it", () => { + const interim = reconcileVoiceTranscript("fix", "the tests"); + expect(interim).toEqual({ value: "fix the tests", range: { start: 4, text: "the tests" } }); + + expect(reconcileVoiceTranscript("fix the tests urgently", "all tests", interim?.range)).toEqual({ + value: "fix all tests urgently", + range: { start: 4, text: "all tests" }, + }); + }); + + it("refuses to overwrite a recognition span the user edited", () => { + const interim = reconcileVoiceTranscript("fix", "the tests"); + expect(reconcileVoiceTranscript("fix those tests", "all tests", interim?.range)).toBeUndefined(); + }); + + it("preserves existing trailing whitespace before the first result", () => { + expect(reconcileVoiceTranscript("fix ", "the tests")).toEqual({ + value: "fix the tests", + range: { start: 5, text: "the tests" }, + }); + }); +}); diff --git a/apps/companion/src/runtime/voice-transcript.ts b/apps/companion/src/runtime/voice-transcript.ts new file mode 100644 index 00000000..18693448 --- /dev/null +++ b/apps/companion/src/runtime/voice-transcript.ts @@ -0,0 +1,33 @@ +export interface VoiceTranscriptRange { + readonly start: number; + readonly text: string; +} + +export interface VoiceTranscriptUpdate { + readonly range: VoiceTranscriptRange; + readonly value: string; +} + +export function reconcileVoiceTranscript( + value: string, + transcript: string, + previous?: VoiceTranscriptRange, +): VoiceTranscriptUpdate | undefined { + const text = transcript.trim(); + if (text === "") return undefined; + + if (previous === undefined) { + const separator = value === "" || /\s$/u.test(value) ? "" : " "; + const start = value.length + separator.length; + return { value: `${value}${separator}${text}`, range: { start, text } }; + } + + if (value.slice(previous.start, previous.start + previous.text.length) !== previous.text) { + return undefined; + } + + return { + value: `${value.slice(0, previous.start)}${text}${value.slice(previous.start + previous.text.length)}`, + range: { start: previous.start, text }, + }; +} diff --git a/apps/companion/src/theme.ts b/apps/companion/src/theme.ts new file mode 100644 index 00000000..a1e9fe63 --- /dev/null +++ b/apps/companion/src/theme.ts @@ -0,0 +1,31 @@ +export const colors = { + background: "#090b0f", + surface: "#11151b", + surfaceRaised: "#171c23", + border: "#272d36", + text: "#f4f5f7", + textMuted: "#9aa3af", + textDim: "#68717e", + blue: "#2f80ed", + bluePressed: "#246bc8", + green: "#35c98b", + amber: "#f2b84b", + red: "#ff6b6b", +} as const; + +export const spacing = { + xxs: 4, + xs: 8, + sm: 12, + md: 16, + lg: 24, + xl: 32, + xxl: 48, +} as const; + +export const radius = { + sm: 8, + md: 12, + lg: 16, + pill: 999, +} as const; diff --git a/apps/companion/tsconfig.json b/apps/companion/tsconfig.json new file mode 100644 index 00000000..ba6a22d6 --- /dev/null +++ b/apps/companion/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "allowImportingTsExtensions": true, + "types": [ + "node" + ], + "paths": { + "@/*": [ + "./src/*" + ], + "@/assets/*": [ + "./assets/*" + ] + } + }, + "include": [ + "src/app/**/*.ts", + "src/app/**/*.tsx", + "src/components/attention-row.tsx", + "src/components/connection-pill.tsx", + "src/components/session-row.tsx", + "src/runtime/**/*.ts", + "src/runtime/**/*.tsx", + "src/theme.ts", + ".expo/types/**/*.ts", + "expo-env.d.ts" + ] +} diff --git a/apps/mobile/README.md b/apps/mobile/README.md deleted file mode 100644 index c42a05f7..00000000 --- a/apps/mobile/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# T4 Code Mobile - -Android thin-client shell for the bundled T4 Code web application. The APK contains the UI assets and connects over secure WebSocket to a separately running T4 Tailnet gateway; it never embeds or starts an OMP appserver. - -## Build - -```bash -pnpm build:web -pnpm --filter @t4-code/mobile build:android:debug -``` - -The installable debug APK is written to `android/app/build/outputs/apk/debug/app-debug.apk`. - -A release build can be signed without committing credentials by setting all four variables before `pnpm --filter @t4-code/mobile build:android:release`: - -```text -T4_ANDROID_KEYSTORE_PATH -T4_ANDROID_KEYSTORE_PASSWORD -T4_ANDROID_KEY_ALIAS -T4_ANDROID_KEY_PASSWORD -``` - -Without those variables Gradle still creates an unsigned release APK for CI inspection. The mobile shell deliberately has no Capacitor `server.url`; `cap sync` copies `apps/web/dist` into the native application. diff --git a/apps/mobile/android/.gitignore b/apps/mobile/android/.gitignore deleted file mode 100644 index 48354a3d..00000000 --- a/apps/mobile/android/.gitignore +++ /dev/null @@ -1,101 +0,0 @@ -# Using Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore - -# Built application files -*.apk -*.aar -*.ap_ -*.aab - -# Files for the ART/Dalvik VM -*.dex - -# Java class files -*.class - -# Generated files -bin/ -gen/ -out/ -# Uncomment the following line in case you need and you don't have the release build type files in your app -# release/ - -# Gradle files -.gradle/ -build/ - -# Local configuration file (sdk path, etc) -local.properties - -# Proguard folder generated by Eclipse -proguard/ - -# Log Files -*.log - -# Android Studio Navigation editor temp files -.navigation/ - -# Android Studio captures folder -captures/ - -# IntelliJ -*.iml -.idea/workspace.xml -.idea/tasks.xml -.idea/gradle.xml -.idea/assetWizardSettings.xml -.idea/dictionaries -.idea/libraries -# Android Studio 3 in .gitignore file. -.idea/caches -.idea/modules.xml -# Comment next line if keeping position of elements in Navigation Editor is relevant for you -.idea/navEditor.xml - -# Keystore files -# Uncomment the following lines if you do not want to check your keystore files in. -#*.jks -#*.keystore - -# External native build folder generated in Android Studio 2.2 and later -.externalNativeBuild -.cxx/ - -# Google Services (e.g. APIs or Firebase) -# google-services.json - -# Freeline -freeline.py -freeline/ -freeline_project_description.json - -# fastlane -fastlane/report.xml -fastlane/Preview.html -fastlane/screenshots -fastlane/test_output -fastlane/readme.md - -# Version control -vcs.xml - -# lint -lint/intermediates/ -lint/generated/ -lint/outputs/ -lint/tmp/ -# lint/reports/ - -# Android Profiling -*.hprof - -# Cordova plugins for Capacitor -capacitor-cordova-android-plugins - -# Copied web assets -app/src/main/assets/public - -# Generated Config files -app/src/main/assets/capacitor.config.json -app/src/main/assets/capacitor.plugins.json -app/src/main/res/xml/config.xml diff --git a/apps/mobile/android/app/.gitignore b/apps/mobile/android/app/.gitignore deleted file mode 100644 index 043df802..00000000 --- a/apps/mobile/android/app/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -/build/* -!/build/.npmkeep diff --git a/apps/mobile/android/app/build.gradle b/apps/mobile/android/app/build.gradle deleted file mode 100644 index 9e683c68..00000000 --- a/apps/mobile/android/app/build.gradle +++ /dev/null @@ -1,91 +0,0 @@ -import groovy.json.JsonSlurper - -apply plugin: 'com.android.application' - -def mobilePackage = new JsonSlurper().parse(rootProject.file('../package.json')) -def mobileVersionName = System.getenv('T4_ANDROID_VERSION_NAME') ?: mobilePackage.version -def versionParts = mobileVersionName.tokenize('.').collect { part -> - def match = part =~ /^(\d+)/ - match.find() ? match.group(1).toInteger() : 0 -} -while (versionParts.size() < 3) versionParts.add(0) -def defaultVersionCode = versionParts[0] * 1000000 + versionParts[1] * 10000 + versionParts[2] -def mobileVersionCode = (System.getenv('T4_ANDROID_VERSION_CODE') ?: defaultVersionCode.toString()).toInteger() - -def releaseKeystorePath = System.getenv('T4_ANDROID_KEYSTORE_PATH') -def releaseKeystorePassword = System.getenv('T4_ANDROID_KEYSTORE_PASSWORD') -def releaseKeyAlias = System.getenv('T4_ANDROID_KEY_ALIAS') -def releaseKeyPassword = System.getenv('T4_ANDROID_KEY_PASSWORD') -def hasReleaseSigning = [ - releaseKeystorePath, - releaseKeystorePassword, - releaseKeyAlias, - releaseKeyPassword, -].every { value -> value != null && !value.isBlank() } - -android { - namespace = "com.lycaonsolutions.t4code" - compileSdk = rootProject.ext.compileSdkVersion - buildFeatures { - buildConfig = true - } - defaultConfig { - applicationId "com.lycaonsolutions.t4code" - minSdkVersion rootProject.ext.minSdkVersion - targetSdkVersion rootProject.ext.targetSdkVersion - versionCode mobileVersionCode - versionName mobileVersionName - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - aaptOptions { - // Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps. - // Default: https://android.googlesource.com/platform/frameworks/base/+/282e181b58cf72b6ca770dc7ca5f91f135444502/tools/aapt/AaptAssets.cpp#61 - ignoreAssetsPattern = '!.svn:!.git:!.ds_store:!*.scc:.*:!CVS:!thumbs.db:!picasa.ini:!*~' - } - } - signingConfigs { - if (hasReleaseSigning) { - release { - storeFile file(releaseKeystorePath) - storePassword releaseKeystorePassword - keyAlias releaseKeyAlias - keyPassword releaseKeyPassword - } - } - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - if (hasReleaseSigning) signingConfig signingConfigs.release - } - } -} - -repositories { - flatDir{ - dirs '../capacitor-cordova-android-plugins/src/main/libs', 'libs' - } -} - -dependencies { - implementation fileTree(include: ['*.jar'], dir: 'libs') - implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" - implementation "androidx.coordinatorlayout:coordinatorlayout:$androidxCoordinatorLayoutVersion" - implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion" - implementation project(':capacitor-android') - testImplementation "junit:junit:$junitVersion" - androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" - androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" - implementation project(':capacitor-cordova-android-plugins') -} - -apply from: 'capacitor.build.gradle' - -try { - def servicesJSON = file('google-services.json') - if (servicesJSON.text) { - apply plugin: 'com.google.gms.google-services' - } -} catch(Exception e) { - logger.info("google-services.json not found, google-services plugin not applied. Push Notifications won't work") -} diff --git a/apps/mobile/android/app/capacitor.build.gradle b/apps/mobile/android/app/capacitor.build.gradle deleted file mode 100644 index bbfb44fa..00000000 --- a/apps/mobile/android/app/capacitor.build.gradle +++ /dev/null @@ -1,19 +0,0 @@ -// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN - -android { - compileOptions { - sourceCompatibility JavaVersion.VERSION_21 - targetCompatibility JavaVersion.VERSION_21 - } -} - -apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" -dependencies { - - -} - - -if (hasProperty('postBuildExtras')) { - postBuildExtras() -} diff --git a/apps/mobile/android/app/proguard-rules.pro b/apps/mobile/android/app/proguard-rules.pro deleted file mode 100644 index f1b42451..00000000 --- a/apps/mobile/android/app/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile diff --git a/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/AppIdentityTest.java b/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/AppIdentityTest.java deleted file mode 100644 index ef13627a..00000000 --- a/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/AppIdentityTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.lycaonsolutions.t4code; - -import static org.junit.Assert.assertEquals; - -import android.content.Context; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public final class AppIdentityTest { - @Test - public void applicationIdMatchesReleaseIdentity() { - Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); - assertEquals("com.lycaonsolutions.t4code", context.getPackageName()); - } -} diff --git a/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/T4SecureStorageInstrumentationTest.java b/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/T4SecureStorageInstrumentationTest.java deleted file mode 100644 index 8228df30..00000000 --- a/apps/mobile/android/app/src/androidTest/java/com/lycaonsolutions/t4code/T4SecureStorageInstrumentationTest.java +++ /dev/null @@ -1,283 +0,0 @@ -package com.lycaonsolutions.t4code; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -import android.content.Context; -import android.content.SharedPreferences; -import android.os.SystemClock; -import android.webkit.WebView; - -import androidx.test.core.app.ActivityScenario; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.platform.app.InstrumentationRegistry; - -import com.getcapacitor.Bridge; - -import org.json.JSONObject; -import org.json.JSONTokener; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.nio.charset.StandardCharsets; -import java.security.KeyStore; -import java.security.MessageDigest; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicReference; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import android.util.Base64; - -@RunWith(AndroidJUnit4.class) -public final class T4SecureStorageInstrumentationTest { - private static final String PREFERENCES_NAME = "t4_code_secure_storage"; - private static final String LEGACY_IV = "credentials_iv"; - private static final String LEGACY_PAYLOAD = "credentials_payload"; - private static final String KEY_ALIAS = "t4_code_device_credentials_v1"; - private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; - private static final String TRANSFORMATION = "AES/GCM/NoPadding"; - private static final String HOST_A = "android-instrumentation-host-a"; - private static final String HOST_B = "android-instrumentation-host-b"; - - private ActivityScenario scenario; - private Context targetContext; - - @Before - public void setUp() throws Exception { - targetContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - targetContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE).edit().clear().commit(); - scenario = ActivityScenario.launch(MainActivity.class); - awaitPluginReady(); - } - - @After - public void tearDown() { - if (scenario != null) scenario.close(); - } - - @Test - public void hostScopedCredentialsPersistAcrossRecreationAndDeleteOnlyOneHost() throws Exception { - invokeSet(HOST_A, "synthetic-device-a", "synthetic-token-a"); - invokeSet(HOST_B, "synthetic-device-b", "synthetic-token-b"); - - assertCredentials(HOST_A, "synthetic-device-a", "synthetic-token-a"); - assertCredentials(HOST_B, "synthetic-device-b", "synthetic-token-b"); - - scenario.recreate(); - awaitPluginReady(); - assertCredentials(HOST_A, "synthetic-device-a", "synthetic-token-a"); - assertCredentials(HOST_B, "synthetic-device-b", "synthetic-token-b"); - - invokeClear(HOST_A); - assertNullCredentials(HOST_A); - assertCredentials(HOST_B, "synthetic-device-b", "synthetic-token-b"); - } - - @Test - public void hostBoundAadRejectsCiphertextCopiedAcrossHosts() throws Exception { - invokeSet(HOST_A, "aad-device-a", "aad-token-a"); - - SharedPreferences preferences = targetContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); - String sourceSuffix = preferenceSuffix(HOST_A); - String targetSuffix = preferenceSuffix(HOST_B); - String iv = preferences.getString("credentials_iv_" + sourceSuffix, null); - String payload = preferences.getString("credentials_payload_" + sourceSuffix, null); - assertNotNull(iv); - assertNotNull(payload); - assertTrue(preferences.edit() - .putString("credentials_iv_" + targetSuffix, iv) - .putString("credentials_payload_" + targetSuffix, payload) - .commit()); - - JSONObject rejected = invokeGet(HOST_B, false); - assertFalse(rejected.getBoolean("ok")); - assertEquals("Stored credentials could not be decrypted.", rejected.getString("message")); - assertCredentials(HOST_A, "aad-device-a", "aad-token-a"); - assertNullCredentials(HOST_B); - } - - @Test - public void legacyV1CredentialsMigrateToHostScopedStorageWithoutLeavingLegacyState() throws Exception { - // Generate the production key through the real plugin path before seeding legacy v1 state. - invokeSet(HOST_A, "temporary-device", "temporary-token"); - invokeClear(HOST_A); - SharedPreferences preferences = targetContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); - assertTrue(preferences.edit().clear().commit()); - seedLegacyCredentials("legacy-device", "legacy-token"); - - assertNullCredentials(HOST_A); - assertCredentials(HOST_A, "legacy-device", "legacy-token", true); - - assertNull(preferences.getString(LEGACY_IV, null)); - assertNull(preferences.getString(LEGACY_PAYLOAD, null)); - String suffix = preferenceSuffix(HOST_A); - assertNotNull(preferences.getString("credentials_iv_" + suffix, null)); - assertNotNull(preferences.getString("credentials_payload_" + suffix, null)); - - scenario.recreate(); - awaitPluginReady(); - assertCredentials(HOST_A, "legacy-device", "legacy-token"); - } - - @Test - public void nativeResumeEventIsObservableAfterBackgroundAndResume() throws Exception { - runJavascript("window.__t4NativeResumeCount = 0; window.addEventListener('t4:native-resume', () => { window.__t4NativeResumeCount += 1; }); 'installed';"); - - scenario.moveToState(androidx.lifecycle.Lifecycle.State.STARTED); - scenario.moveToState(androidx.lifecycle.Lifecycle.State.RESUMED); - - long deadline = SystemClock.uptimeMillis() + 5_000L; - int count = 0; - while (SystemClock.uptimeMillis() < deadline) { - String value = readJavascript("String(window.__t4NativeResumeCount || 0)"); - count = Integer.parseInt(value); - if (count > 0) break; - SystemClock.sleep(100L); - } - assertTrue("native resume event was not observed", count > 0); - } - - private void invokeSet(String hostKey, String deviceId, String deviceToken) throws Exception { - JSONObject result = invoke(String.format(""" - const plugin = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.T4SecureStorage; - if (!plugin) throw new Error('T4SecureStorage plugin unavailable'); - await plugin.setCredentials({hostKey: %s, deviceId: %s, deviceToken: %s}); - """, JSONObject.quote(hostKey), JSONObject.quote(deviceId), JSONObject.quote(deviceToken))); - assertTrue(result.getBoolean("ok")); - } - - private void invokeClear(String hostKey) throws Exception { - JSONObject result = invoke(String.format(""" - const plugin = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.T4SecureStorage; - if (!plugin) throw new Error('T4SecureStorage plugin unavailable'); - await plugin.clearCredentials({hostKey: %s}); - """, JSONObject.quote(hostKey))); - assertTrue(result.getBoolean("ok")); - } - - private void assertCredentials(String hostKey, String deviceId, String deviceToken) throws Exception { - assertCredentials(hostKey, deviceId, deviceToken, false); - } - - private void assertCredentials(String hostKey, String deviceId, String deviceToken, boolean migrateLegacy) throws Exception { - JSONObject result = invokeGet(hostKey, migrateLegacy); - assertTrue(result.getBoolean("ok")); - JSONObject credentials = result.getJSONObject("value"); - assertEquals(deviceId, credentials.getString("deviceId")); - assertEquals(deviceToken, credentials.getString("deviceToken")); - } - - private void assertNullCredentials(String hostKey) throws Exception { - JSONObject result = invokeGet(hostKey, false); - assertTrue(result.getBoolean("ok")); - assertTrue(result.isNull("value")); - } - - private JSONObject invokeGet(String hostKey, boolean migrateLegacy) throws Exception { - return invoke(String.format(""" - const plugin = window.Capacitor && window.Capacitor.Plugins && window.Capacitor.Plugins.T4SecureStorage; - if (!plugin) throw new Error('T4SecureStorage plugin unavailable'); - const response = await plugin.getCredentials({hostKey: %s, migrateLegacy: %s}); - return response.credentials === null ? null : response.credentials; - """, JSONObject.quote(hostKey), migrateLegacy ? "true" : "false")); - } - - private JSONObject invoke(String body) throws Exception { - runJavascript(String.format(""" - window.__t4InstrumentationResult = null; - (async () => { - try { - const value = await (async () => { %s })(); - window.__t4InstrumentationResult = JSON.stringify({ok: true, value: value === undefined ? null : value}); - } catch (error) { - window.__t4InstrumentationResult = JSON.stringify({ok: false, message: String(error && error.message || error)}); - } - })(); - 'started'; - """, body)); - long deadline = SystemClock.uptimeMillis() + 10_000L; - while (SystemClock.uptimeMillis() < deadline) { - String encoded = readJavascript("window.__t4InstrumentationResult || ''"); - if (!encoded.isEmpty()) return new JSONObject(encoded); - SystemClock.sleep(100L); - } - throw new AssertionError("Timed out waiting for native plugin result"); - } - - private void awaitPluginReady() throws Exception { - long deadline = SystemClock.uptimeMillis() + 20_000L; - while (SystemClock.uptimeMillis() < deadline) { - if ("ready".equals(readJavascript("typeof window.Capacitor?.Plugins?.T4SecureStorage === 'object' ? 'ready' : 'pending'"))) return; - SystemClock.sleep(100L); - } - throw new AssertionError("T4SecureStorage plugin did not become available"); - } - - private void seedLegacyCredentials(String deviceId, String deviceToken) throws Exception { - KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); - keyStore.load(null); - SecretKey key = (SecretKey) keyStore.getKey(KEY_ALIAS, null); - assertNotNull(key); - - Cipher cipher = Cipher.getInstance(TRANSFORMATION); - cipher.init(Cipher.ENCRYPT_MODE, key); - String plaintext = new JSONObject().put("deviceId", deviceId).put("deviceToken", deviceToken).toString(); - byte[] payload = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); - SharedPreferences preferences = targetContext.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); - assertTrue(preferences.edit() - .putString(LEGACY_IV, Base64.encodeToString(cipher.getIV(), Base64.NO_WRAP)) - .putString(LEGACY_PAYLOAD, Base64.encodeToString(payload, Base64.NO_WRAP)) - .commit()); - } - - private String preferenceSuffix(String hostKey) throws Exception { - byte[] digest = MessageDigest.getInstance("SHA-256").digest(hostKey.getBytes(StandardCharsets.UTF_8)); - StringBuilder suffix = new StringBuilder(digest.length * 2); - for (byte value : digest) suffix.append(String.format("%02x", value & 0xff)); - return suffix.toString(); - } - - private void runJavascript(String script) throws Exception { - CountDownLatch latch = new CountDownLatch(1); - AtomicReference failure = new AtomicReference<>(); - scenario.onActivity(activity -> { - Bridge bridge = activity.getBridge(); - if (bridge == null || bridge.getWebView() == null) { - failure.set(new AssertionError("Capacitor bridge WebView unavailable")); - latch.countDown(); - return; - } - bridge.getWebView().evaluateJavascript(script, ignored -> latch.countDown()); - }); - if (!latch.await(5, TimeUnit.SECONDS)) throw new AssertionError("Timed out evaluating JavaScript"); - if (failure.get() != null) throw new AssertionError(failure.get()); - } - - private String readJavascript(String script) throws Exception { - CountDownLatch latch = new CountDownLatch(1); - AtomicReference result = new AtomicReference<>(); - AtomicReference failure = new AtomicReference<>(); - scenario.onActivity(activity -> { - WebView webView = activity.getBridge().getWebView(); - webView.evaluateJavascript(script, value -> { - try { - Object decoded = new JSONTokener(value).nextValue(); - result.set(decoded == null ? "" : String.valueOf(decoded)); - } catch (Exception error) { - failure.set(error); - } - latch.countDown(); - }); - }); - if (!latch.await(5, TimeUnit.SECONDS)) throw new AssertionError("Timed out reading JavaScript"); - if (failure.get() != null) throw new AssertionError(failure.get()); - return result.get(); - } -} diff --git a/apps/mobile/android/app/src/main/AndroidManifest.xml b/apps/mobile/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index e7136e88..00000000 --- a/apps/mobile/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/MainActivity.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/MainActivity.java deleted file mode 100644 index 06b8b010..00000000 --- a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/MainActivity.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.lycaonsolutions.t4code; - -import android.os.Bundle; - -import com.getcapacitor.BridgeActivity; - -public class MainActivity extends BridgeActivity { - private static final String APP_RESUME_EVENT = "t4:native-resume"; - - @Override - public void onCreate(Bundle savedInstanceState) { - registerPlugin(T4SecureStoragePlugin.class); - registerPlugin(T4UpdatePlugin.class); - registerPlugin(T4SpeechPlugin.class); - super.onCreate(savedInstanceState); - } - - @Override - public void onResume() { - super.onResume(); - if (getBridge() != null) { - getBridge().triggerWindowJSEvent(APP_RESUME_EVENT); - } - } -} diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4FileProvider.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4FileProvider.java deleted file mode 100644 index 612c1081..00000000 --- a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4FileProvider.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.lycaonsolutions.t4code; - -import androidx.core.content.FileProvider; - -/** App-scoped provider for handing one verified update to Android's installer. */ -public final class T4FileProvider extends FileProvider {} diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SecureStoragePlugin.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SecureStoragePlugin.java deleted file mode 100644 index dd81c60f..00000000 --- a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SecureStoragePlugin.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.lycaonsolutions.t4code; - -import android.content.Context; -import android.content.SharedPreferences; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyProperties; -import android.util.Base64; - -import com.getcapacitor.JSObject; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.annotation.CapacitorPlugin; - -import org.json.JSONObject; - -import java.nio.charset.StandardCharsets; -import java.security.KeyStore; -import java.security.MessageDigest; - -import javax.crypto.Cipher; -import javax.crypto.KeyGenerator; -import javax.crypto.SecretKey; -import javax.crypto.spec.GCMParameterSpec; - -@CapacitorPlugin(name = "T4SecureStorage") -public final class T4SecureStoragePlugin extends Plugin { - private static final String KEYSTORE_PROVIDER = "AndroidKeyStore"; - private static final String KEY_ALIAS = "t4_code_device_credentials_v1"; - private static final String CIPHER_TRANSFORMATION = "AES/GCM/NoPadding"; - private static final String PREFERENCES_NAME = "t4_code_secure_storage"; - private static final String PREFERENCE_IV = "credentials_iv"; - private static final String PREFERENCE_PAYLOAD = "credentials_payload"; - private static final String PREFERENCE_IV_PREFIX = "credentials_iv_"; - private static final String PREFERENCE_PAYLOAD_PREFIX = "credentials_payload_"; - private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray(); - private static final int GCM_TAG_BITS = 128; - private static final int MAX_HOST_KEY_LENGTH = 2048; - private static final int MAX_DEVICE_ID_LENGTH = 256; - private static final int MAX_DEVICE_TOKEN_LENGTH = 512; - - @PluginMethod - public void getCredentials(PluginCall call) { - String hostKey = call.getString("hostKey"); - if (!isBoundedText(hostKey, MAX_HOST_KEY_LENGTH)) { - call.reject("Invalid host key."); - return; - } - boolean migrateLegacy = Boolean.TRUE.equals(call.getBoolean("migrateLegacy", false)); - - synchronized (this) { - try { - JSObject result = new JSObject(); - JSObject credentials = readCredentials(hostKey, migrateLegacy); - result.put("credentials", credentials == null ? JSONObject.NULL : credentials); - call.resolve(result); - } catch (Exception error) { - clearStoredStateBestEffort(hostKey); - call.reject("Stored credentials could not be decrypted."); - } - } - } - - @PluginMethod - public void setCredentials(PluginCall call) { - String hostKey = call.getString("hostKey"); - String deviceId = call.getString("deviceId"); - String deviceToken = call.getString("deviceToken"); - if (!isBoundedText(hostKey, MAX_HOST_KEY_LENGTH) - || !isBoundedText(deviceId, MAX_DEVICE_ID_LENGTH) - || !isBoundedText(deviceToken, MAX_DEVICE_TOKEN_LENGTH)) { - call.reject("Invalid device credentials."); - return; - } - - synchronized (this) { - try { - JSObject credentials = new JSObject(); - credentials.put("deviceId", deviceId); - credentials.put("deviceToken", deviceToken); - storeCredentials(hostKey, credentials.toString()); - call.resolve(); - } catch (Exception error) { - call.reject("Device credentials could not be stored."); - } - } - } - - @PluginMethod - public void clearCredentials(PluginCall call) { - String hostKey = call.getString("hostKey"); - if (!isBoundedText(hostKey, MAX_HOST_KEY_LENGTH)) { - call.reject("Invalid host key."); - return; - } - - synchronized (this) { - try { - clearStoredState(hostKey); - call.resolve(); - } catch (Exception error) { - call.reject("Device credentials could not be cleared."); - } - } - } - - private boolean isBoundedText(String value, int maxLength) { - if (value == null || value.isEmpty() || value.length() > maxLength) return false; - for (int index = 0; index < value.length(); index += 1) { - char character = value.charAt(index); - if (character <= 0x1f || character == 0x7f) return false; - } - return true; - } - - private SharedPreferences preferences() { - return getContext().getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE); - } - - private String preferenceIv(String hostKey) throws Exception { - return PREFERENCE_IV_PREFIX + preferenceSuffix(hostKey); - } - - private String preferencePayload(String hostKey) throws Exception { - return PREFERENCE_PAYLOAD_PREFIX + preferenceSuffix(hostKey); - } - - private String preferenceSuffix(String hostKey) throws Exception { - byte[] digest = MessageDigest.getInstance("SHA-256") - .digest(hostKey.getBytes(StandardCharsets.UTF_8)); - StringBuilder suffix = new StringBuilder(digest.length * 2); - for (byte value : digest) { - suffix.append(HEX_DIGITS[(value >>> 4) & 0x0f]); - suffix.append(HEX_DIGITS[value & 0x0f]); - } - return suffix.toString(); - } - - private SecretKey getOrCreateKey() throws Exception { - KeyStore keyStore = KeyStore.getInstance(KEYSTORE_PROVIDER); - keyStore.load(null); - if (keyStore.containsAlias(KEY_ALIAS)) { - return (SecretKey) keyStore.getKey(KEY_ALIAS, null); - } - - KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, KEYSTORE_PROVIDER); - KeyGenParameterSpec specification = new KeyGenParameterSpec.Builder( - KEY_ALIAS, - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT - ) - .setBlockModes(KeyProperties.BLOCK_MODE_GCM) - .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) - .setKeySize(256) - .build(); - generator.init(specification); - return generator.generateKey(); - } - - private void storeCredentials(String hostKey, String plaintext) throws Exception { - storeCredentials(hostKey, plaintext, false); - } - - private void storeCredentials(String hostKey, String plaintext, boolean removeLegacy) throws Exception { - Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); - cipher.init(Cipher.ENCRYPT_MODE, getOrCreateKey()); - cipher.updateAAD(hostKey.getBytes(StandardCharsets.UTF_8)); - byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); - - SharedPreferences.Editor editor = preferences().edit() - .putString(preferenceIv(hostKey), Base64.encodeToString(cipher.getIV(), Base64.NO_WRAP)) - .putString(preferencePayload(hostKey), Base64.encodeToString(ciphertext, Base64.NO_WRAP)); - if (removeLegacy) { - editor - .remove(PREFERENCE_IV) - .remove(PREFERENCE_PAYLOAD); - } - if (!editor.commit()) throw new IllegalStateException("secure preferences commit failed"); - } - - private JSObject readCredentials(String hostKey, boolean migrateLegacy) throws Exception { - SharedPreferences storedPreferences = preferences(); - String encodedIv = storedPreferences.getString(preferenceIv(hostKey), null); - String encodedPayload = storedPreferences.getString(preferencePayload(hostKey), null); - if (encodedIv != null || encodedPayload != null) { - if (encodedIv == null || encodedPayload == null) { - throw new IllegalStateException("incomplete keyed credential state"); - } - return decryptCredentials(encodedIv, encodedPayload, hostKey); - } - if (!migrateLegacy) return null; - - String legacyIv = storedPreferences.getString(PREFERENCE_IV, null); - String legacyPayload = storedPreferences.getString(PREFERENCE_PAYLOAD, null); - if (legacyIv == null && legacyPayload == null) return null; - if (legacyIv == null || legacyPayload == null) { - throw new IllegalStateException("incomplete legacy credential state"); - } - - JSObject credentials = decryptCredentials(legacyIv, legacyPayload, null); - storeCredentials(hostKey, credentials.toString(), true); - return credentials; - } - - private JSObject decryptCredentials(String encodedIv, String encodedPayload, String hostKey) throws Exception { - byte[] iv = Base64.decode(encodedIv, Base64.NO_WRAP); - byte[] ciphertext = Base64.decode(encodedPayload, Base64.NO_WRAP); - Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION); - cipher.init(Cipher.DECRYPT_MODE, getOrCreateKey(), new GCMParameterSpec(GCM_TAG_BITS, iv)); - if (hostKey != null) cipher.updateAAD(hostKey.getBytes(StandardCharsets.UTF_8)); - String plaintext = new String(cipher.doFinal(ciphertext), StandardCharsets.UTF_8); - JSObject credentials = JSObject.fromJSONObject(new JSONObject(plaintext)); - - String deviceId = credentials.getString("deviceId"); - String deviceToken = credentials.getString("deviceToken"); - if (!isBoundedText(deviceId, MAX_DEVICE_ID_LENGTH) || !isBoundedText(deviceToken, MAX_DEVICE_TOKEN_LENGTH)) { - throw new IllegalStateException("invalid secure credential payload"); - } - return credentials; - } - - private void clearStoredState(String hostKey) throws Exception { - if (!preferences().edit() - .remove(preferenceIv(hostKey)) - .remove(preferencePayload(hostKey)) - .commit()) { - throw new IllegalStateException("secure preferences clear failed"); - } - } - - private void clearStoredStateBestEffort(String hostKey) { - try { - clearStoredState(hostKey); - } catch (Exception ignored) { - // The caller still receives a generic failure; never expose credential details. - } - } -} diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SpeechPlugin.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SpeechPlugin.java deleted file mode 100644 index 80abf664..00000000 --- a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4SpeechPlugin.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.lycaonsolutions.t4code; - -import android.speech.tts.TextToSpeech; -import android.speech.tts.UtteranceProgressListener; -import com.getcapacitor.JSObject; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.annotation.CapacitorPlugin; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; - -@CapacitorPlugin(name = "T4Speech") -public final class T4SpeechPlugin extends Plugin { - private final Object lock = new Object(); - private final Map pending = new HashMap<>(); - private TextToSpeech textToSpeech; - private boolean ready; - private boolean destroyed; - - @Override public void load() { - synchronized (lock) { - destroyed = false; ready = false; - textToSpeech = new TextToSpeech(getContext(), status -> { - synchronized (lock) { if (!destroyed && textToSpeech != null) { ready = status == TextToSpeech.SUCCESS; if (ready) textToSpeech.setLanguage(Locale.getDefault()); } } - }); - textToSpeech.setOnUtteranceProgressListener(new UtteranceProgressListener() { - @Override public void onStart(String id) { } - @Override public void onDone(String id) { finish(id, true, null); } - @Override public void onError(String id) { finish(id, false, "Speech synthesis failed"); } - @Override public void onStop(String id, boolean interrupted) { finish(id, false, "Speech cancelled"); } - }); - } - } - - @PluginMethod public void speakText(PluginCall call) { - String text = call.getString("text"); - int max = TextToSpeech.getMaxSpeechInputLength(); - if (text == null || text.length() == 0 || text.length() > max || text.indexOf('\0') >= 0) { resolve(call, false, "invalid speech text"); return; } - synchronized (lock) { - if (destroyed || !ready || textToSpeech == null) { resolve(call, false, "Speech synthesis is unavailable"); return; } - stopPendingLocked(); - String id = UUID.randomUUID().toString(); pending.put(id, call); - int status = textToSpeech.speak(text, TextToSpeech.QUEUE_FLUSH, null, id); - if (status != TextToSpeech.SUCCESS) { pending.remove(id); resolve(call, false, "Speech synthesis is unavailable"); } - } - } - - @PluginMethod public void stopSpeaking(PluginCall call) { - synchronized (lock) { if (textToSpeech != null) textToSpeech.stop(); stopPendingLocked(); resolve(call, !destroyed, destroyed ? "Speech service is stopped" : null); } - } - - private void finish(String id, boolean accepted, String error) { synchronized (lock) { PluginCall call = pending.remove(id); if (call != null) resolve(call, accepted, error); } } - private void stopPendingLocked() { for (PluginCall call : pending.values()) resolve(call, false, "Speech cancelled"); pending.clear(); } - private static void resolve(PluginCall call, boolean accepted, String error) { JSObject result = new JSObject(); result.put("accepted", accepted); if (error != null) result.put("error", error); call.resolve(result); } - - @Override protected void handleOnDestroy() { synchronized (lock) { destroyed = true; ready = false; if (textToSpeech != null) { textToSpeech.stop(); textToSpeech.shutdown(); textToSpeech = null; } stopPendingLocked(); } super.handleOnDestroy(); } -} diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateFileStore.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateFileStore.java deleted file mode 100644 index 2c720a98..00000000 --- a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateFileStore.java +++ /dev/null @@ -1,228 +0,0 @@ -package com.lycaonsolutions.t4code; - -import java.io.File; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** - * Owns the app-private APK cache used by the updater. - * - * A normal verified APK is temporary. An {@code -installer.apk} file is the - * one exception: it is retained while Android's package installer owns a - * read-only content URI, then removed when T4 returns to the foreground. - */ -final class T4UpdateFileStore { - private static final String PARTIAL_SUFFIX = ".apk.partial"; - private static final String APK_SUFFIX = ".apk"; - private static final String HANDOFF_SUFFIX = "-installer.apk"; - private static final Object OWNERSHIP_LOCK = new Object(); - private static final Map ACTIVE_OWNERS = new HashMap<>(); - - private final File directory; - private final String ownershipKey; - private final Object ownerToken = new Object(); - private File activeHandoff; - - T4UpdateFileStore(File directory) { - this.directory = directory; - ownershipKey = normalizedPath(directory); - synchronized (OWNERSHIP_LOCK) { - ACTIVE_OWNERS.put(ownershipKey, ownerToken); - } - } - - /** - * Removes interrupted downloads and unhanded verified packages. At most - * one prior installer handoff survives until the activity is foregrounded. - */ - synchronized File prepareOnStartup() throws IOException { - synchronized (OWNERSHIP_LOCK) { - requireOwnership(); - ensureDirectory(); - File keep = newestHandoff(); - cleanExcept(keep); - activeHandoff = keep != null && keep.isFile() ? keep : null; - return activeHandoff; - } - } - - /** The user is foregrounded and starting a new download, so no old handoff is live. */ - synchronized void prepareForDownload() throws IOException { - synchronized (OWNERSHIP_LOCK) { - requireOwnership(); - ensureDirectory(); - cleanExcept(null); - activeHandoff = null; - } - } - - synchronized File createPartial(String version) throws IOException { - synchronized (OWNERSHIP_LOCK) { - requireOwnership(); - ensureDirectory(); - return File.createTempFile("T4-Code-" + version + "-", PARTIAL_SUFFIX, directory); - } - } - - synchronized File finalizeVerified(File partial) throws IOException { - synchronized (OWNERSHIP_LOCK) { - requireOwnership(); - requireManagedFile(partial, PARTIAL_SUFFIX); - String partialName = partial.getName(); - File verified = new File(directory, partialName.substring(0, partialName.length() - ".partial".length())); - deleteIfPresent(verified); - if (!partial.renameTo(verified)) { - throw new IOException("could not finalize verified update"); - } - if (!verified.setReadOnly()) { - deleteIfPresent(verified); - throw new IOException("could not protect verified update"); - } - return verified; - } - } - - /** - * Renames the verified file before its URI is granted. The distinctive - * suffix is the process-death marker that lets a new plugin instance keep - * exactly this one file until T4 is foregrounded again. - */ - synchronized File beginInstallerHandoff(File verified) throws IOException { - synchronized (OWNERSHIP_LOCK) { - requireOwnership(); - requireManagedFile(verified, APK_SUFFIX); - if (isHandoff(verified)) throw new IOException("update is already handed to the installer"); - String name = verified.getName(); - File handoff = new File(directory, name.substring(0, name.length() - APK_SUFFIX.length()) + HANDOFF_SUFFIX); - cleanHandoffsExcept(null); - deleteIfPresent(handoff); - if (!verified.renameTo(handoff)) { - throw new IOException("could not prepare update for the installer"); - } - activeHandoff = handoff; - return handoff; - } - } - - synchronized void finishInstallerHandoff(File handoff) throws IOException { - synchronized (OWNERSHIP_LOCK) { - requireOwnership(); - requireManagedFileOrMissing(handoff, HANDOFF_SUFFIX); - deleteIfPresent(handoff); - if (sameFile(activeHandoff, handoff)) activeHandoff = null; - } - } - - synchronized void discard(File file) { - synchronized (OWNERSHIP_LOCK) { - if (!ownsDirectory() || file == null || !isDirectChild(file)) return; - file.delete(); - if (sameFile(activeHandoff, file)) activeHandoff = null; - } - } - - /** Destroy may interrupt a worker, but must not revoke a URI already owned by the installer. */ - synchronized void cleanupForDestroy() { - synchronized (OWNERSHIP_LOCK) { - if (!ownsDirectory()) return; - try { - ensureDirectory(); - cleanExcept(activeHandoff); - } catch (IOException ignored) { - // Startup and pre-download sweeps retry cleanup on the next plugin instance. - } - } - } - - synchronized File activeHandoff() { - synchronized (OWNERSHIP_LOCK) { - return ownsDirectory() ? activeHandoff : null; - } - } - - private void requireOwnership() throws IOException { - if (!ownsDirectory()) throw new IOException("update storage belongs to a newer Android activity"); - } - - private boolean ownsDirectory() { - return ACTIVE_OWNERS.get(ownershipKey) == ownerToken; - } - - private static String normalizedPath(File directory) { - try { - return directory.getCanonicalPath(); - } catch (IOException ignored) { - return directory.getAbsoluteFile().toURI().normalize().getPath(); - } - } - - private void ensureDirectory() throws IOException { - if ((!directory.isDirectory() && !directory.mkdirs()) || !directory.isDirectory()) { - throw new IOException("could not create private update directory"); - } - } - - private File newestHandoff() throws IOException { - File newest = null; - for (File entry : entries()) { - if (!entry.isFile() || !isHandoff(entry)) continue; - if ( - newest == null || - entry.lastModified() > newest.lastModified() || - (entry.lastModified() == newest.lastModified() && entry.getName().compareTo(newest.getName()) > 0) - ) { - newest = entry; - } - } - return newest; - } - - private void cleanExcept(File keep) throws IOException { - for (File entry : entries()) { - if (sameFile(entry, keep)) continue; - deleteIfPresent(entry); - } - } - - private void cleanHandoffsExcept(File keep) throws IOException { - for (File entry : entries()) { - if (!isHandoff(entry) || sameFile(entry, keep)) continue; - deleteIfPresent(entry); - } - } - - private File[] entries() throws IOException { - File[] entries = directory.listFiles(); - if (entries == null) throw new IOException("could not inspect private update directory"); - return entries; - } - - private void requireManagedFile(File file, String suffix) throws IOException { - requireManagedFileOrMissing(file, suffix); - if (!file.isFile()) throw new IOException("update file is missing"); - } - - private void requireManagedFileOrMissing(File file, String suffix) throws IOException { - if (file == null || !isDirectChild(file) || !file.getName().endsWith(suffix)) { - throw new IOException("update file is outside the private update directory"); - } - } - - private boolean isDirectChild(File file) { - File parent = file.getAbsoluteFile().getParentFile(); - return parent != null && parent.equals(directory.getAbsoluteFile()); - } - - private boolean isHandoff(File file) { - return file.getName().endsWith(HANDOFF_SUFFIX); - } - - private boolean sameFile(File first, File second) { - return first != null && second != null && first.getAbsoluteFile().equals(second.getAbsoluteFile()); - } - - private void deleteIfPresent(File file) throws IOException { - if (file.exists() && !file.delete()) throw new IOException("could not remove stale update file"); - } -} diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdatePlugin.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdatePlugin.java deleted file mode 100644 index 4563f4ea..00000000 --- a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdatePlugin.java +++ /dev/null @@ -1,696 +0,0 @@ -package com.lycaonsolutions.t4code; - -import android.content.ClipData; -import android.content.Intent; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.Signature; -import android.content.pm.SigningInfo; -import android.net.Uri; -import android.os.Build; - -import androidx.core.content.FileProvider; - -import com.getcapacitor.JSObject; -import com.getcapacitor.Plugin; -import com.getcapacitor.PluginCall; -import com.getcapacitor.PluginMethod; -import com.getcapacitor.annotation.CapacitorPlugin; - -import org.json.JSONArray; -import org.json.JSONObject; - -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Set; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import javax.net.ssl.HttpsURLConnection; - -/** - * User-driven Android release checks. The WebView never supplies a URL: this - * plugin validates T4's first-party manifest, downloads only its exact APK, - * verifies the bytes and Android package identity, and then asks the system - * package installer to prompt the user. - */ -@CapacitorPlugin(name = "T4Update") -public final class T4UpdatePlugin extends Plugin { - private static final String MANIFEST_URL = "https://t4code.net/releases/latest.json"; - private static final String EXPECTED_PACKAGE_ID = "com.lycaonsolutions.t4code"; - private static final String UPDATE_CACHE_DIRECTORY = "t4-updates"; - private static final String APK_MIME_TYPE = "application/vnd.android.package-archive"; - private static final String STATE_CHANGED_EVENT = "stateChanged"; - private static final int NETWORK_TIMEOUT_MS = 8_000; - private static final int MAX_MANIFEST_BYTES = 64 * 1024; - private static final long MAX_ASSET_BYTES = 1024L * 1024L * 1024L; - private static final int MAX_ASSET_REDIRECTS = 4; - private static final int MAX_ERROR_LENGTH = 240; - private final T4UpdateStateMachine updateState = new T4UpdateStateMachine(); - private String latestVersion; - private Long checkedAt; - private String errorMessage; - private String statusMessage; - private ManifestRelease validatedRelease; - private T4UpdateFileStore updateFiles; - private File installerHandoff; - private boolean installerWasPaused; - private boolean recoveredHandoff; - private volatile boolean destroyed; - private final ExecutorService downloadExecutor = Executors.newSingleThreadExecutor(runnable -> { - Thread thread = new Thread(runnable, "T4VerifiedUpdate"); - thread.setDaemon(true); - return thread; - }); - - @Override - public void load() { - T4UpdateFileStore files = new T4UpdateFileStore( - new File(getContext().getCacheDir(), UPDATE_CACHE_DIRECTORY) - ); - File recovered = null; - try { - recovered = files.prepareOnStartup(); - } catch (Exception ignored) { - // The foreground download path retries the sweep and reports a safe failure if storage is unavailable. - } - synchronized (this) { - destroyed = false; - updateFiles = files; - installerHandoff = recovered; - recoveredHandoff = recovered != null; - installerWasPaused = false; - } - } - - @PluginMethod - public void getState(PluginCall call) { - synchronized (this) { - call.resolve(statePayload()); - } - } - - @PluginMethod - public void checkForUpdate(PluginCall call) { - JSObject checkingState; - synchronized (this) { - if (!updateState.beginCheck()) { - call.resolve(statePayload()); - return; - } - statusMessage = "Checking the published Android release."; - errorMessage = null; - checkingState = statePayload(); - } - notifyStateChanged(checkingState); - try { - ManifestRelease release = fetchRelease(); - String currentVersion = currentVersion(); - int comparison = T4UpdateVerifier.compareVersions(release.version, currentVersion); - JSObject resultState; - synchronized (this) { - String resultPhase = comparison > 0 ? "available" : "current"; - if (!updateState.finishCheck(resultPhase)) { - call.resolve(statePayload()); - return; - } - latestVersion = release.version; - checkedAt = System.currentTimeMillis(); - errorMessage = null; - statusMessage = null; - if (comparison > 0) { - validatedRelease = release; - } else { - validatedRelease = null; - } - resultState = statePayload(); - } - call.resolve(resultState); - notifyStateChanged(resultState); - } catch (Exception ignored) { - JSObject resultState; - synchronized (this) { - if (!updateState.finishCheck("error")) { - call.resolve(statePayload()); - return; - } - latestVersion = null; - checkedAt = System.currentTimeMillis(); - validatedRelease = null; - statusMessage = null; - errorMessage = boundedError( - "T4 Code could not verify the latest Android release. Check your connection and try again." - ); - resultState = statePayload(); - } - call.resolve(resultState); - notifyStateChanged(resultState); - } - } - - @PluginMethod - public void openUpdate(PluginCall call) { - final ManifestRelease release; - final JSObject startedState; - synchronized (this) { - release = validatedRelease; - T4UpdateStateMachine.DownloadStart start = updateState.beginDownload(release != null); - if (start != T4UpdateStateMachine.DownloadStart.STARTED) { - if ( - start == T4UpdateStateMachine.DownloadStart.BUSY || - start == T4UpdateStateMachine.DownloadStart.HANDED_OFF - ) { - call.resolve(statePayload()); - } else { - call.reject("Check for an available update first."); - } - return; - } - errorMessage = null; - statusMessage = "Downloading the published APK for verification."; - startedState = statePayload(); - } - call.resolve(startedState); - notifyStateChanged(startedState); - - try { - downloadExecutor.execute(() -> { - File packageFile = null; - try { - if (destroyed) return; - packageFile = downloadVerifiedPackage(release); - verifyAndroidPackage(packageFile, release.version); - if (destroyed) { - discardUpdateFile(packageFile); - return; - } - File verifiedPackage = packageFile; - getBridge().executeOnMainThread(() -> openPackageInstaller(verifiedPackage)); - } catch (Exception ignored) { - discardUpdateFile(packageFile); - publishDownloadFailure(); - } - }); - } catch (RuntimeException ignored) { - publishDownloadFailure(); - } - } - - @Override - protected void handleOnPause() { - synchronized (this) { - if (installerHandoff != null && "installer".equals(updateState.phase())) { - installerWasPaused = true; - } - } - } - - @Override - protected void handleOnResume() { - final File completedHandoff; - final JSObject resumedState; - synchronized (this) { - if (installerHandoff == null || (!recoveredHandoff && !installerWasPaused)) return; - completedHandoff = installerHandoff; - installerHandoff = null; - recoveredHandoff = false; - installerWasPaused = false; - if ("installer".equals(updateState.phase())) { - boolean canRetry = validatedRelease != null; - updateState.installerReturned(canRetry); - errorMessage = null; - statusMessage = canRetry - ? "Android's installer closed. You can download the release again." - : null; - resumedState = statePayload(); - } else { - resumedState = null; - } - } - finishInstallerHandoff(completedHandoff); - if (resumedState != null) notifyStateChanged(resumedState); - } - - @Override - protected void handleOnDestroy() { - destroyed = true; - downloadExecutor.shutdownNow(); - T4UpdateFileStore files; - synchronized (this) { - files = updateFiles; - validatedRelease = null; - statusMessage = null; - errorMessage = null; - installerHandoff = null; - recoveredHandoff = false; - installerWasPaused = false; - updateState.reset(); - } - if (files != null) files.cleanupForDestroy(); - } - - private File downloadVerifiedPackage(ManifestRelease release) throws Exception { - T4UpdateFileStore files = requireUpdateFiles(); - files.prepareForDownload(); - File partial = files.createPartial(release.version); - - HttpsURLConnection connection = null; - try { - connection = openAssetConnection(release.apkUrl); - long responseSize = connection.getContentLengthLong(); - if (responseSize >= 0 && responseSize != release.apkSize) { - throw new IllegalStateException("release response size does not match its manifest"); - } - try ( - InputStream input = new BufferedInputStream(connection.getInputStream()); - FileOutputStream fileOutput = new FileOutputStream(partial, false); - BufferedOutputStream output = new BufferedOutputStream(fileOutput) - ) { - T4UpdateVerifier.copyExact(input, output, release.apkSize, release.apkSha256); - fileOutput.getFD().sync(); - } - return files.finalizeVerified(partial); - } catch (Exception error) { - files.discard(partial); - throw error; - } finally { - if (connection != null) connection.disconnect(); - } - } - - private HttpsURLConnection openAssetConnection(String validatedUrl) throws Exception { - URL current = new URL(validatedUrl); - for (int redirects = 0; redirects <= MAX_ASSET_REDIRECTS; redirects += 1) { - T4UpdateVerifier.requireAllowedAssetUrl(current, redirects == 0); - if (redirects == 0 && !validatedUrl.equals(current.toString())) { - throw new IllegalStateException("release asset URL changed before download"); - } - HttpsURLConnection connection = (HttpsURLConnection) current.openConnection(); - connection.setConnectTimeout(NETWORK_TIMEOUT_MS); - connection.setReadTimeout(NETWORK_TIMEOUT_MS); - connection.setInstanceFollowRedirects(false); - connection.setRequestMethod("GET"); - connection.setRequestProperty("Accept", APK_MIME_TYPE); - connection.setRequestProperty("Accept-Encoding", "identity"); - connection.setUseCaches(false); - int status = connection.getResponseCode(); - if (status == HttpsURLConnection.HTTP_OK) return connection; - if (!T4UpdateVerifier.isRedirectStatus(status) || redirects == MAX_ASSET_REDIRECTS) { - connection.disconnect(); - throw new IllegalStateException("release asset response was not successful"); - } - String location = connection.getHeaderField("Location"); - connection.disconnect(); - if (location == null || location.isEmpty() || location.length() > 8192) { - throw new IllegalStateException("release asset redirect is invalid"); - } - current = new URL(current, location); - } - throw new IllegalStateException("release asset redirect limit exceeded"); - } - - private void verifyAndroidPackage(File packageFile, String expectedVersion) throws Exception { - PackageManager manager = getContext().getPackageManager(); - PackageInfo candidate = archivePackageInfo(manager, packageFile); - if (candidate == null || !EXPECTED_PACKAGE_ID.equals(candidate.packageName)) { - throw new IllegalStateException("update package identity does not match T4 Code"); - } - if (!expectedVersion.equals(candidate.versionName)) { - throw new IllegalStateException("update package version does not match its manifest"); - } - PackageInfo installed = installedPackageInfo(manager); - if (!EXPECTED_PACKAGE_ID.equals(installed.packageName)) { - throw new IllegalStateException("installed package identity does not match T4 Code"); - } - boolean trustedSigner; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - SigningEvidence installedSigning = signingEvidence(installed); - SigningEvidence candidateSigning = signingEvidence(candidate); - trustedSigner = T4UpdateVerifier.isTrustedSignerTransition( - installedSigning.current, - installedSigning.history, - installedSigning.multiple, - candidateSigning.current, - candidateSigning.history, - candidateSigning.multiple - ); - } else { - trustedSigner = T4UpdateVerifier.sameSignerSet(legacySigners(installed), legacySigners(candidate)); - } - if (!trustedSigner) { - throw new IllegalStateException("update package signer does not match this installation"); - } - } - - @SuppressWarnings("deprecation") - private PackageInfo installedPackageInfo(PackageManager manager) throws Exception { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - return manager.getPackageInfo( - EXPECTED_PACKAGE_ID, - PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES) - ); - } - int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P - ? PackageManager.GET_SIGNING_CERTIFICATES - : PackageManager.GET_SIGNATURES; - return manager.getPackageInfo(EXPECTED_PACKAGE_ID, flags); - } - - @SuppressWarnings("deprecation") - private PackageInfo archivePackageInfo(PackageManager manager, File packageFile) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - return manager.getPackageArchiveInfo( - packageFile.getAbsolutePath(), - PackageManager.PackageInfoFlags.of(PackageManager.GET_SIGNING_CERTIFICATES) - ); - } - int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P - ? PackageManager.GET_SIGNING_CERTIFICATES - : PackageManager.GET_SIGNATURES; - return manager.getPackageArchiveInfo(packageFile.getAbsolutePath(), flags); - } - - @android.annotation.TargetApi(Build.VERSION_CODES.P) - private SigningEvidence signingEvidence(PackageInfo packageInfo) { - SigningInfo signingInfo = packageInfo.signingInfo; - if (signingInfo == null) return new SigningEvidence(new ArrayList<>(), new ArrayList<>(), false); - boolean multiple = signingInfo.hasMultipleSigners(); - List current = signatureBytes(signingInfo.getApkContentsSigners()); - List history = multiple ? new ArrayList<>() : signatureBytes(signingInfo.getSigningCertificateHistory()); - return new SigningEvidence(current, history, multiple); - } - - @SuppressWarnings("deprecation") - private List legacySigners(PackageInfo packageInfo) { - return signatureBytes(packageInfo.signatures); - } - - private List signatureBytes(Signature[] signatures) { - if (signatures == null) return new ArrayList<>(); - List result = new ArrayList<>(signatures.length); - for (Signature signature : signatures) result.add(signature.toByteArray()); - return result; - } - - private void openPackageInstaller(File verifiedPackage) { - synchronized (this) { - if (destroyed || !"downloading".equals(updateState.phase())) { - discardUpdateFile(verifiedPackage); - return; - } - } - final T4UpdateFileStore files; - final File handoff; - try { - files = requireUpdateFiles(); - handoff = files.beginInstallerHandoff(verifiedPackage); - synchronized (this) { - installerHandoff = handoff; - recoveredHandoff = false; - installerWasPaused = false; - } - Uri contentUri = FileProvider.getUriForFile( - getContext(), - getContext().getPackageName() + ".fileprovider", - handoff - ); - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setDataAndType(contentUri, APK_MIME_TYPE); - intent.setClipData(ClipData.newRawUri("T4 Code update", contentUri)); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - getActivity().startActivity(intent); - } catch (Exception ignored) { - synchronized (this) { - installerHandoff = null; - recoveredHandoff = false; - installerWasPaused = false; - } - discardUpdateFile(verifiedPackage); - T4UpdateFileStore currentFiles; - synchronized (this) { - currentFiles = updateFiles; - } - if (currentFiles != null && currentFiles.activeHandoff() != null) { - finishInstallerHandoff(currentFiles.activeHandoff()); - } - publishDownloadFailure(); - return; - } - - JSObject state; - synchronized (this) { - updateState.installerOpened(); - errorMessage = null; - statusMessage = "The verified APK is open in Android's installer. Android will ask before replacing this installation."; - state = statePayload(); - } - notifyListeners(STATE_CHANGED_EVENT, state); - } - - private T4UpdateFileStore requireUpdateFiles() { - synchronized (this) { - if (updateFiles == null) throw new IllegalStateException("Android update storage is unavailable"); - return updateFiles; - } - } - - private void discardUpdateFile(File file) { - T4UpdateFileStore files; - synchronized (this) { - files = updateFiles; - } - if (files != null) files.discard(file); - else if (file != null) file.delete(); - } - - private void finishInstallerHandoff(File handoff) { - T4UpdateFileStore files; - synchronized (this) { - files = updateFiles; - } - if (files == null || handoff == null) return; - try { - files.finishInstallerHandoff(handoff); - } catch (Exception ignored) { - // The next startup or foreground download retries this bounded one-file cleanup. - } - } - - private void publishDownloadFailure() { - JSObject state; - synchronized (this) { - if (!"downloading".equals(updateState.phase())) return; - updateState.downloadFailed(); - validatedRelease = null; - statusMessage = null; - errorMessage = boundedError( - "T4 Code could not verify and open the Android update. Your current installation is unchanged." - ); - state = statePayload(); - } - notifyStateChanged(state); - } - - private void notifyStateChanged(JSObject state) { - getBridge().executeOnMainThread(() -> notifyListeners(STATE_CHANGED_EVENT, state)); - } - - private JSObject statePayload() { - JSObject result = new JSObject(); - result.put("currentVersion", currentVersion()); - result.put("phase", updateState.phase()); - result.put("revision", updateState.revision()); - if (latestVersion != null) result.put("latestVersion", latestVersion); - if (checkedAt != null) result.put("checkedAt", checkedAt); - if (errorMessage != null) result.put("error", errorMessage); - if (statusMessage != null) result.put("message", statusMessage); - return result; - } - - private String currentVersion() { - if (!EXPECTED_PACKAGE_ID.equals(BuildConfig.APPLICATION_ID)) { - throw new IllegalStateException("Android application identity is invalid"); - } - String version = BuildConfig.VERSION_NAME; - if (!T4UpdateVerifier.isValidVersion(version)) { - throw new IllegalStateException("Android application version is invalid"); - } - return version; - } - - private ManifestRelease fetchRelease() throws Exception { - HttpsURLConnection connection = (HttpsURLConnection) new URL(MANIFEST_URL).openConnection(); - connection.setConnectTimeout(NETWORK_TIMEOUT_MS); - connection.setReadTimeout(NETWORK_TIMEOUT_MS); - connection.setInstanceFollowRedirects(false); - connection.setRequestMethod("GET"); - connection.setRequestProperty("Accept", "application/json"); - connection.setUseCaches(false); - try { - if (connection.getResponseCode() != HttpsURLConnection.HTTP_OK) { - throw new IllegalStateException("update manifest response was not successful"); - } - long declaredLength = connection.getContentLengthLong(); - if (declaredLength > MAX_MANIFEST_BYTES) { - throw new IllegalStateException("update manifest is too large"); - } - byte[] bytes; - try (InputStream input = connection.getInputStream()) { - bytes = readBounded(input); - } - return parseManifest(new JSONObject(new String(bytes, StandardCharsets.UTF_8))); - } finally { - connection.disconnect(); - } - } - - private byte[] readBounded(InputStream input) throws Exception { - ByteArrayOutputStream output = new ByteArrayOutputStream(); - byte[] buffer = new byte[8 * 1024]; - int count; - while ((count = input.read(buffer)) != -1) { - if (output.size() + count > MAX_MANIFEST_BYTES) { - throw new IllegalStateException("update manifest is too large"); - } - output.write(buffer, 0, count); - } - return output.toByteArray(); - } - - private ManifestRelease parseManifest(JSONObject manifest) throws Exception { - requireExactKeys( - manifest, - "schemaVersion", - "channel", - "version", - "tag", - "publishedAt", - "releaseUrl", - "assets" - ); - if (requireJsonInteger(manifest, "schemaVersion") != 1 || !"stable".equals(requireJsonString(manifest, "channel"))) { - throw new IllegalStateException("unsupported update manifest"); - } - - String version = requireJsonString(manifest, "version"); - T4UpdateVerifier.requireManifestReleaseIdentity( - version, - requireJsonString(manifest, "tag"), - requireJsonString(manifest, "releaseUrl"), - requireJsonString(manifest, "publishedAt") - ); - - Object assetsValue = manifest.get("assets"); - if (!(assetsValue instanceof JSONArray)) throw new IllegalStateException("release assets must be an array"); - JSONArray assets = (JSONArray) assetsValue; - if (assets.length() != 5) throw new IllegalStateException("invalid release asset count"); - Set identities = new HashSet<>(); - String apkUrl = null; - Long apkSize = null; - String apkSha256 = null; - for (int index = 0; index < assets.length(); index += 1) { - JSONObject asset = assets.getJSONObject(index); - requireExactKeys(asset, "platform", "kind", "arch", "name", "url", "size", "sha256"); - String platform = requireJsonString(asset, "platform"); - String kind = requireJsonString(asset, "kind"); - String arch = requireJsonString(asset, "arch"); - String name = requireJsonString(asset, "name"); - String url = requireJsonString(asset, "url"); - long size = requireJsonInteger(asset, "size"); - String sha256 = requireJsonString(asset, "sha256"); - String identity = T4UpdateVerifier.requireManifestAsset( - version, - platform, - kind, - arch, - name, - url, - size, - sha256, - MAX_ASSET_BYTES - ); - if (!identities.add(identity)) throw new IllegalStateException("duplicate release asset"); - if ("android:apk:universal".equals(identity)) { - apkUrl = url; - apkSize = size; - apkSha256 = sha256; - } - } - if (identities.size() != 5 || apkUrl == null || apkSize == null || apkSha256 == null) { - throw new IllegalStateException("Android release asset is missing"); - } - return new ManifestRelease(version, apkUrl, apkSize, apkSha256); - } - - private String requireJsonString(JSONObject object, String key) throws Exception { - Object value = object.get(key); - if (!(value instanceof String)) throw new IllegalStateException(key + " must be a string"); - return (String) value; - } - - private long requireJsonInteger(JSONObject object, String key) throws Exception { - Object value = object.get(key); - if (!(value instanceof Number)) throw new IllegalStateException(key + " must be an integer"); - Number number = (Number) value; - long integer = number.longValue(); - double numeric = number.doubleValue(); - if (Double.isNaN(numeric) || Double.isInfinite(numeric) || numeric != (double) integer) { - throw new IllegalStateException(key + " must be an integer"); - } - return integer; - } - - private void requireExactKeys(JSONObject object, String... expected) { - Set keys = new HashSet<>(); - Iterator iterator = object.keys(); - while (iterator.hasNext()) keys.add(iterator.next()); - Set allowed = new HashSet<>(); - for (String key : expected) allowed.add(key); - if (!keys.equals(allowed)) throw new IllegalStateException("unexpected update manifest fields"); - } - - private String boundedError(String message) { - StringBuilder output = new StringBuilder(); - for (int index = 0; index < message.length() && output.length() < MAX_ERROR_LENGTH; index += 1) { - char character = message.charAt(index); - output.append(character <= 0x1f || character == 0x7f ? ' ' : character); - } - return output.toString(); - } - - private static final class ManifestRelease { - private final String version; - private final String apkUrl; - private final long apkSize; - private final String apkSha256; - - private ManifestRelease(String version, String apkUrl, long apkSize, String apkSha256) { - this.version = version; - this.apkUrl = apkUrl; - this.apkSize = apkSize; - this.apkSha256 = apkSha256; - } - } - - private static final class SigningEvidence { - private final List current; - private final List history; - private final boolean multiple; - - private SigningEvidence(List current, List history, boolean multiple) { - this.current = current; - this.history = history; - this.multiple = multiple; - } - } -} diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateStateMachine.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateStateMachine.java deleted file mode 100644 index af87bd32..00000000 --- a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateStateMachine.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.lycaonsolutions.t4code; - -/** Small synchronized state machine so bridge calls and the download worker cannot race. */ -final class T4UpdateStateMachine { - enum DownloadStart { - STARTED, - BUSY, - HANDED_OFF, - UNAVAILABLE, - } - - private String phase = "idle"; - private long revision; - private boolean downloadInProgress; - - synchronized String phase() { - return phase; - } - - synchronized long revision() { - return revision; - } - - synchronized boolean beginCheck() { - if (downloadInProgress || "checking".equals(phase) || "installer".equals(phase)) return false; - transition("checking"); - return true; - } - - synchronized boolean finishCheck(String resultPhase) { - if (!("available".equals(resultPhase) || "current".equals(resultPhase) || "error".equals(resultPhase))) { - throw new IllegalArgumentException("invalid check result phase"); - } - if (downloadInProgress || !"checking".equals(phase)) return false; - transition(resultPhase); - return true; - } - - synchronized DownloadStart beginDownload(boolean hasValidatedRelease) { - if (downloadInProgress || "downloading".equals(phase)) return DownloadStart.BUSY; - if ("installer".equals(phase)) return DownloadStart.HANDED_OFF; - if (!hasValidatedRelease || !"available".equals(phase)) return DownloadStart.UNAVAILABLE; - downloadInProgress = true; - transition("downloading"); - return DownloadStart.STARTED; - } - - synchronized void installerOpened() { - requireActiveDownload(); - downloadInProgress = false; - transition("installer"); - } - - synchronized void installerReturned(boolean updateStillAvailable) { - if (!"installer".equals(phase) || downloadInProgress) { - throw new IllegalStateException("no installer handoff is active"); - } - transition(updateStillAvailable ? "available" : "idle"); - } - - synchronized void downloadFailed() { - requireActiveDownload(); - downloadInProgress = false; - transition("error"); - } - - synchronized void reset() { - downloadInProgress = false; - transition("idle"); - } - - private void requireActiveDownload() { - if (!downloadInProgress || !"downloading".equals(phase)) { - throw new IllegalStateException("no verified update download is active"); - } - } - - private void transition(String nextPhase) { - phase = nextPhase; - revision += 1; - } -} diff --git a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateVerifier.java b/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateVerifier.java deleted file mode 100644 index f3c8d3da..00000000 --- a/apps/mobile/android/app/src/main/java/com/lycaonsolutions/t4code/T4UpdateVerifier.java +++ /dev/null @@ -1,251 +0,0 @@ -package com.lycaonsolutions.t4code; - -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; - -/** Pure-Java verification primitives shared by the Android updater and JVM tests. */ -final class T4UpdateVerifier { - private static final int COPY_BUFFER_BYTES = 64 * 1024; - private static final String RELEASE_DOWNLOAD_ROOT = "https://github.com/LycaonLLC/t4-code/releases/download/"; - private static final String RELEASE_PAGE_ROOT = "https://github.com/LycaonLLC/t4-code/releases/tag/"; - private static final Pattern VERSION_PATTERN = Pattern.compile( - "^(?:0|[1-9][0-9]{0,5})\\.(?:0|[1-9][0-9]{0,5})\\.(?:0|[1-9][0-9]{0,5})$" - ); - private static final Pattern SHA256_PATTERN = Pattern.compile("^[0-9a-f]{64}$"); - private static final Pattern PUBLISHED_AT_PATTERN = Pattern.compile( - "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(?:\\.[0-9]{1,9})?Z$" - ); - - private T4UpdateVerifier() {} - - static void copyExact( - InputStream input, - OutputStream output, - long expectedSize, - String expectedSha256 - ) throws Exception { - if (expectedSize <= 0) throw new IllegalArgumentException("expected size must be positive"); - if (!isValidSha256(expectedSha256)) { - throw new IllegalArgumentException("expected SHA-256 is invalid"); - } - - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] buffer = new byte[COPY_BUFFER_BYTES]; - long total = 0; - int count; - while ((count = input.read(buffer)) != -1) { - if (count == 0) continue; - if (count > expectedSize - total) { - throw new IllegalStateException("downloaded package exceeds its declared size"); - } - output.write(buffer, 0, count); - digest.update(buffer, 0, count); - total += count; - } - if (total != expectedSize) { - throw new IllegalStateException("downloaded package size does not match its manifest"); - } - String actualSha256 = lowercaseHex(digest.digest()); - if (!MessageDigest.isEqual( - expectedSha256.getBytes(java.nio.charset.StandardCharsets.US_ASCII), - actualSha256.getBytes(java.nio.charset.StandardCharsets.US_ASCII) - )) { - throw new IllegalStateException("downloaded package digest does not match its manifest"); - } - output.flush(); - } - - static boolean sameSignerSet(List installed, List candidate) throws Exception { - if (installed == null || candidate == null || installed.isEmpty() || candidate.isEmpty()) return false; - Set installedFingerprints = signerFingerprints(installed); - Set candidateFingerprints = signerFingerprints(candidate); - return installedFingerprints.size() == installed.size() && - candidateFingerprints.size() == candidate.size() && - installedFingerprints.equals(candidateFingerprints); - } - - /** - * Accepts the same signer, or a forward single-signer rotation whose - * PackageManager-verified candidate history contains the installed current - * signer. Multi-signer packages cannot rotate and must match exactly. - */ - static boolean isTrustedSignerTransition( - List installedCurrent, - List installedHistory, - boolean installedHasMultipleSigners, - List candidateCurrent, - List candidateHistory, - boolean candidateHasMultipleSigners - ) throws Exception { - if (installedHasMultipleSigners || candidateHasMultipleSigners) { - return installedHasMultipleSigners && - candidateHasMultipleSigners && - sameSignerSet(installedCurrent, candidateCurrent); - } - if (installedCurrent == null || candidateCurrent == null || installedCurrent.size() != 1 || candidateCurrent.size() != 1) { - return false; - } - - List installedLineage = orderedSignerFingerprints(installedHistory); - List candidateLineage = orderedSignerFingerprints(candidateHistory); - if (installedLineage.isEmpty() || candidateLineage.isEmpty()) return false; - String installedSigner = signerFingerprint(installedCurrent.get(0)); - String candidateSigner = signerFingerprint(candidateCurrent.get(0)); - if (installedSigner == null || candidateSigner == null) return false; - if (!installedSigner.equals(installedLineage.get(installedLineage.size() - 1))) return false; - if (!candidateSigner.equals(candidateLineage.get(candidateLineage.size() - 1))) return false; - if (new HashSet<>(installedLineage).size() != installedLineage.size()) return false; - if (new HashSet<>(candidateLineage).size() != candidateLineage.size()) return false; - if (installedSigner.equals(candidateSigner)) return true; - - int installedSignerInCandidateHistory = candidateLineage.indexOf(installedSigner); - return installedSignerInCandidateHistory >= 0 && - installedSignerInCandidateHistory < candidateLineage.size() - 1; - } - - static int compareVersions(String left, String right) { - int[] leftParts = strictVersionParts(left); - int[] rightParts = strictVersionParts(right); - for (int index = 0; index < leftParts.length; index += 1) { - int comparison = Integer.compare(leftParts[index], rightParts[index]); - if (comparison != 0) return comparison; - } - return 0; - } - - static String expectedAssetName(String version, String platform, String kind, String arch) { - strictVersionParts(version); - String identity = platform + ":" + kind + ":" + arch; - switch (identity) { - case "android:apk:universal": - return "T4-Code-" + version + "-android.apk"; - case "linux:deb:x86_64": - return "T4-Code-" + version + "-linux-amd64.deb"; - case "linux:appimage:x86_64": - return "T4-Code-" + version + "-linux-x86_64.AppImage"; - case "mac:dmg:arm64": - return "T4-Code-" + version + "-mac-arm64.dmg"; - case "mac:zip:arm64": - return "T4-Code-" + version + "-mac-arm64.zip"; - default: - throw new IllegalArgumentException("unknown release asset"); - } - } - - static void requireManifestReleaseIdentity( - String version, - String tag, - String releaseUrl, - String publishedAt - ) { - strictVersionParts(version); - String expectedTag = "v" + version; - if (!expectedTag.equals(tag)) throw new IllegalArgumentException("release tag mismatch"); - if (!(RELEASE_PAGE_ROOT + expectedTag).equals(releaseUrl)) { - throw new IllegalArgumentException("release page mismatch"); - } - if (publishedAt == null || publishedAt.length() > 64 || !PUBLISHED_AT_PATTERN.matcher(publishedAt).matches()) { - throw new IllegalArgumentException("invalid release timestamp"); - } - } - - static String requireManifestAsset( - String version, - String platform, - String kind, - String arch, - String name, - String url, - long size, - String sha256, - long maximumSize - ) { - String identity = platform + ":" + kind + ":" + arch; - String expectedName = expectedAssetName(version, platform, kind, arch); - if (!expectedName.equals(name)) throw new IllegalArgumentException("release asset name mismatch"); - String expectedUrl = RELEASE_DOWNLOAD_ROOT + "v" + version + "/" + expectedName; - if (!expectedUrl.equals(url)) throw new IllegalArgumentException("release asset URL mismatch"); - if (size <= 0 || size > maximumSize) throw new IllegalArgumentException("invalid release asset size"); - if (!isValidSha256(sha256)) throw new IllegalArgumentException("invalid release asset digest"); - return identity; - } - - static void requireAllowedAssetUrl(URL url, boolean initial) { - if (!"https".equals(url.getProtocol()) || url.getUserInfo() != null || (url.getPort() != -1 && url.getPort() != 443)) { - throw new IllegalArgumentException("release asset connection is not secure"); - } - String host = url.getHost().toLowerCase(java.util.Locale.ROOT); - boolean trustedDownloadHost = "release-assets.githubusercontent.com".equals(host) || - "objects.githubusercontent.com".equals(host); - if (initial ? !"github.com".equals(host) : !("github.com".equals(host) || trustedDownloadHost)) { - throw new IllegalArgumentException("release asset host is not allowed"); - } - } - - static boolean isRedirectStatus(int status) { - return status == HttpURLConnection.HTTP_MOVED_PERM || - status == HttpURLConnection.HTTP_MOVED_TEMP || - status == HttpURLConnection.HTTP_SEE_OTHER || - status == 307 || - status == 308; - } - - static boolean isValidSha256(String value) { - return value != null && SHA256_PATTERN.matcher(value).matches(); - } - - static boolean isValidVersion(String value) { - return value != null && VERSION_PATTERN.matcher(value).matches(); - } - - private static Set signerFingerprints(List certificates) throws Exception { - Set fingerprints = new HashSet<>(); - for (byte[] certificate : certificates) { - if (certificate == null || certificate.length == 0) return new HashSet<>(); - fingerprints.add(lowercaseHex(MessageDigest.getInstance("SHA-256").digest(certificate))); - } - return fingerprints; - } - - private static List orderedSignerFingerprints(List certificates) throws Exception { - List fingerprints = new ArrayList<>(); - if (certificates == null) return fingerprints; - for (byte[] certificate : certificates) { - String fingerprint = signerFingerprint(certificate); - if (fingerprint == null) return new ArrayList<>(); - fingerprints.add(fingerprint); - } - return fingerprints; - } - - private static String signerFingerprint(byte[] certificate) throws Exception { - if (certificate == null || certificate.length == 0) return null; - return lowercaseHex(MessageDigest.getInstance("SHA-256").digest(certificate)); - } - - private static int[] strictVersionParts(String version) { - if (version == null || !VERSION_PATTERN.matcher(version).matches()) { - throw new IllegalArgumentException("release version is invalid"); - } - String[] values = version.split("\\."); - return new int[] { - Integer.parseInt(values[0]), - Integer.parseInt(values[1]), - Integer.parseInt(values[2]), - }; - } - - private static String lowercaseHex(byte[] value) { - StringBuilder result = new StringBuilder(value.length * 2); - for (byte item : value) result.append(String.format(java.util.Locale.ROOT, "%02x", item & 0xff)); - return result.toString(); - } -} diff --git a/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png deleted file mode 100644 index 59844232..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-hdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png deleted file mode 100644 index 5fe4dd61..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-mdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png deleted file mode 100644 index 5c013652..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-xhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png deleted file mode 100644 index 8343f586..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-xxhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png deleted file mode 100644 index 9c1e83e8..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-land-xxxhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png deleted file mode 100644 index 9fe5ea4a..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-hdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png deleted file mode 100644 index aecf44b2..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-mdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png deleted file mode 100644 index 593eb334..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-xhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png deleted file mode 100644 index e0a06420..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-xxhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png b/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png deleted file mode 100644 index 468d279b..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable-port-xxxhdpi/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index c7bd21db..00000000 --- a/apps/mobile/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - diff --git a/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml b/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index d5fccc53..00000000 --- a/apps/mobile/android/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/mobile/android/app/src/main/res/drawable/splash.png b/apps/mobile/android/app/src/main/res/drawable/splash.png deleted file mode 100644 index 5fe4dd61..00000000 Binary files a/apps/mobile/android/app/src/main/res/drawable/splash.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/layout/activity_main.xml b/apps/mobile/android/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index b5ad1387..00000000 --- a/apps/mobile/android/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml deleted file mode 100644 index 036d09bc..00000000 --- a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml deleted file mode 100644 index 036d09bc..00000000 --- a/apps/mobile/android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 04e56297..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png deleted file mode 100644 index ba8eafc3..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png deleted file mode 100644 index 04e56297..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index 2e8a85cd..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png deleted file mode 100644 index 04344b6f..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png deleted file mode 100644 index 2e8a85cd..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 8e1579f7..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png deleted file mode 100644 index 8f46c7ff..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png deleted file mode 100644 index 8e1579f7..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 4e4e05be..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 8262c741..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png deleted file mode 100644 index 0d1adac8..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 7854e90f..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png deleted file mode 100644 index 54aca261..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png deleted file mode 100644 index 7854e90f..00000000 Binary files a/apps/mobile/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ diff --git a/apps/mobile/android/app/src/main/res/values/colors.xml b/apps/mobile/android/app/src/main/res/values/colors.xml deleted file mode 100644 index 08788749..00000000 --- a/apps/mobile/android/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - #E83174 - #0D0D0D - #E83174 - diff --git a/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml b/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml deleted file mode 100644 index 06781cec..00000000 --- a/apps/mobile/android/app/src/main/res/values/ic_launcher_background.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - #0D0D0D - diff --git a/apps/mobile/android/app/src/main/res/values/strings.xml b/apps/mobile/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 23cb0f2b..00000000 --- a/apps/mobile/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - T4 Code - T4 Code - com.lycaonsolutions.t4code - com.lycaonsolutions.t4code - diff --git a/apps/mobile/android/app/src/main/res/values/styles.xml b/apps/mobile/android/app/src/main/res/values/styles.xml deleted file mode 100644 index be874e54..00000000 --- a/apps/mobile/android/app/src/main/res/values/styles.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/apps/mobile/android/app/src/main/res/xml/file_paths.xml b/apps/mobile/android/app/src/main/res/xml/file_paths.xml deleted file mode 100644 index b904c664..00000000 --- a/apps/mobile/android/app/src/main/res/xml/file_paths.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateFileStoreTest.java b/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateFileStoreTest.java deleted file mode 100644 index 8e2ffa0b..00000000 --- a/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateFileStoreTest.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.lycaonsolutions.t4code; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -public final class T4UpdateFileStoreTest { - @Rule - public final TemporaryFolder temporaryFolder = new TemporaryFolder(); - - @Test - public void startupRemovesPartialsAndVerifiedFilesButKeepsOneHandoff() throws Exception { - File directory = temporaryFolder.newFolder("updates"); - File partial = write(directory, "T4-Code-1.2.3-a.apk.partial"); - File verified = write(directory, "T4-Code-1.2.3-b.apk"); - File olderHandoff = write(directory, "T4-Code-1.2.2-a-installer.apk"); - File newerHandoff = write(directory, "T4-Code-1.2.3-b-installer.apk"); - assertTrue(olderHandoff.setLastModified(1_000)); - assertTrue(newerHandoff.setLastModified(2_000)); - - T4UpdateFileStore store = new T4UpdateFileStore(directory); - File retained = store.prepareOnStartup(); - - assertEquals(newerHandoff.getAbsoluteFile(), retained.getAbsoluteFile()); - assertFalse(partial.exists()); - assertFalse(verified.exists()); - assertFalse(olderHandoff.exists()); - assertTrue(newerHandoff.exists()); - assertEquals(1, fileCount(directory)); - } - - @Test - public void foregroundDownloadClearsAProcessDeathHandoff() throws Exception { - File directory = temporaryFolder.newFolder("updates"); - File oldHandoff = write(directory, "T4-Code-1.2.2-a-installer.apk"); - T4UpdateFileStore store = new T4UpdateFileStore(directory); - assertNotNull(store.prepareOnStartup()); - - store.prepareForDownload(); - - assertFalse(oldHandoff.exists()); - assertNull(store.activeHandoff()); - assertEquals(0, fileCount(directory)); - } - - @Test - public void installerHandoffSurvivesDestroyThenIsRemovedOnReturn() throws Exception { - File directory = temporaryFolder.newFolder("updates"); - T4UpdateFileStore store = new T4UpdateFileStore(directory); - store.prepareForDownload(); - File partial = store.createPartial("1.2.3"); - writeBytes(partial); - File verified = store.finalizeVerified(partial); - File handoff = store.beginInstallerHandoff(verified); - File interruptedAfterHandoff = store.createPartial("1.2.4"); - writeBytes(interruptedAfterHandoff); - - assertFalse(partial.exists()); - assertFalse(verified.exists()); - assertTrue(handoff.exists()); - assertTrue(handoff.getName().endsWith("-installer.apk")); - - store.cleanupForDestroy(); - assertTrue(handoff.exists()); - assertFalse(interruptedAfterHandoff.exists()); - assertEquals(1, fileCount(directory)); - - T4UpdateFileStore recreated = new T4UpdateFileStore(directory); - File recovered = recreated.prepareOnStartup(); - assertEquals(handoff.getAbsoluteFile(), recovered.getAbsoluteFile()); - recreated.finishInstallerHandoff(recovered); - - assertFalse(handoff.exists()); - assertNull(recreated.activeHandoff()); - assertEquals(0, fileCount(directory)); - } - - @Test - public void destroyRemovesInterruptedAndUnhandedPackages() throws Exception { - File directory = temporaryFolder.newFolder("updates"); - T4UpdateFileStore store = new T4UpdateFileStore(directory); - store.prepareForDownload(); - File partial = store.createPartial("1.2.3"); - writeBytes(partial); - File verified = store.finalizeVerified(partial); - File secondPartial = store.createPartial("1.2.4"); - writeBytes(secondPartial); - - store.cleanupForDestroy(); - - assertFalse(partial.exists()); - assertFalse(verified.exists()); - assertFalse(secondPartial.exists()); - assertEquals(0, fileCount(directory)); - } - - @Test - public void staleActivityCannotSweepANewerInstallerHandoff() throws Exception { - File directory = temporaryFolder.newFolder("updates"); - T4UpdateFileStore stale = new T4UpdateFileStore(directory); - stale.prepareOnStartup(); - - T4UpdateFileStore current = new T4UpdateFileStore(directory); - current.prepareOnStartup(); - current.prepareForDownload(); - File partial = current.createPartial("1.2.3"); - writeBytes(partial); - File handoff = current.beginInstallerHandoff(current.finalizeVerified(partial)); - - assertThrows(IOException.class, stale::prepareForDownload); - stale.cleanupForDestroy(); - stale.discard(handoff); - - assertTrue(handoff.exists()); - assertEquals(handoff.getAbsoluteFile(), current.activeHandoff().getAbsoluteFile()); - assertEquals(1, fileCount(directory)); - } - - private static File write(File directory, String name) throws Exception { - File file = new File(directory, name); - writeBytes(file); - return file; - } - - private static void writeBytes(File file) throws Exception { - try (FileOutputStream output = new FileOutputStream(file)) { - output.write(new byte[] { 1, 2, 3, 4 }); - output.getFD().sync(); - } - } - - private static int fileCount(File directory) { - File[] files = directory.listFiles(); - return files == null ? 0 : files.length; - } -} diff --git a/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateStateMachineTest.java b/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateStateMachineTest.java deleted file mode 100644 index 930e7e9c..00000000 --- a/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateStateMachineTest.java +++ /dev/null @@ -1,130 +0,0 @@ -package com.lycaonsolutions.t4code; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; - -import org.junit.Test; - -public final class T4UpdateStateMachineTest { - @Test - public void onlyOneConcurrentDownloadCanStart() throws Exception { - T4UpdateStateMachine state = new T4UpdateStateMachine(); - assertTrue(state.beginCheck()); - assertTrue(state.finishCheck("available")); - - int workers = 12; - ExecutorService executor = Executors.newFixedThreadPool(workers); - CountDownLatch start = new CountDownLatch(1); - List> attempts = new ArrayList<>(); - for (int index = 0; index < workers; index += 1) { - attempts.add(executor.submit(() -> { - start.await(); - return state.beginDownload(true); - })); - } - start.countDown(); - - int started = 0; - int busy = 0; - for (Future attempt : attempts) { - T4UpdateStateMachine.DownloadStart result = attempt.get(); - if (result == T4UpdateStateMachine.DownloadStart.STARTED) started += 1; - else if (result == T4UpdateStateMachine.DownloadStart.BUSY) busy += 1; - } - executor.shutdownNow(); - - assertEquals(1, started); - assertEquals(workers - 1, busy); - assertEquals("downloading", state.phase()); - assertFalse(state.beginCheck()); - } - - @Test - public void concurrentChecksProduceOneNativeTransition() throws Exception { - T4UpdateStateMachine state = new T4UpdateStateMachine(); - int workers = 12; - ExecutorService executor = Executors.newFixedThreadPool(workers); - CountDownLatch start = new CountDownLatch(1); - List> attempts = new ArrayList<>(); - for (int index = 0; index < workers; index += 1) { - attempts.add(executor.submit(() -> { - start.await(); - return state.beginCheck(); - })); - } - start.countDown(); - - int started = 0; - for (Future attempt : attempts) { - if (attempt.get()) started += 1; - } - executor.shutdownNow(); - - assertEquals(1, started); - assertEquals("checking", state.phase()); - assertEquals(1, state.revision()); - } - - @Test - public void staleCheckCompletionCannotReplaceAResetState() { - T4UpdateStateMachine state = new T4UpdateStateMachine(); - assertTrue(state.beginCheck()); - state.reset(); - long resetRevision = state.revision(); - - assertFalse(state.finishCheck("available")); - assertEquals("idle", state.phase()); - assertEquals(resetRevision, state.revision()); - } - - @Test - public void installerHandoffCannotStartAReplacementDownload() { - T4UpdateStateMachine state = new T4UpdateStateMachine(); - assertTrue(state.beginCheck()); - assertTrue(state.finishCheck("available")); - assertEquals(T4UpdateStateMachine.DownloadStart.STARTED, state.beginDownload(true)); - state.installerOpened(); - - assertEquals("installer", state.phase()); - long handoffRevision = state.revision(); - assertEquals(T4UpdateStateMachine.DownloadStart.HANDED_OFF, state.beginDownload(true)); - assertEquals(handoffRevision, state.revision()); - assertFalse(state.beginCheck()); - } - - @Test - public void installerReturnAllowsTheVerifiedReleaseToBeRetried() { - T4UpdateStateMachine state = new T4UpdateStateMachine(); - assertTrue(state.beginCheck()); - assertTrue(state.finishCheck("available")); - assertEquals(T4UpdateStateMachine.DownloadStart.STARTED, state.beginDownload(true)); - state.installerOpened(); - state.installerReturned(true); - - assertEquals("available", state.phase()); - assertEquals(T4UpdateStateMachine.DownloadStart.STARTED, state.beginDownload(true)); - } - - @Test - public void failedDownloadRequiresANewSuccessfulCheck() { - T4UpdateStateMachine state = new T4UpdateStateMachine(); - assertTrue(state.beginCheck()); - assertTrue(state.finishCheck("available")); - assertEquals(T4UpdateStateMachine.DownloadStart.STARTED, state.beginDownload(true)); - state.downloadFailed(); - - assertEquals("error", state.phase()); - assertEquals(T4UpdateStateMachine.DownloadStart.UNAVAILABLE, state.beginDownload(true)); - assertTrue(state.beginCheck()); - assertTrue(state.finishCheck("current")); - assertEquals("current", state.phase()); - } -} diff --git a/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateVerifierTest.java b/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateVerifierTest.java deleted file mode 100644 index 82404196..00000000 --- a/apps/mobile/android/app/src/test/java/com/lycaonsolutions/t4code/T4UpdateVerifierTest.java +++ /dev/null @@ -1,355 +0,0 @@ -package com.lycaonsolutions.t4code; - -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThrows; -import static org.junit.Assert.assertTrue; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.nio.charset.StandardCharsets; -import java.net.URL; -import java.security.MessageDigest; -import java.util.Arrays; -import java.util.Collections; - -import org.junit.Test; - -public final class T4UpdateVerifierTest { - @Test - public void exactStreamAcceptsOnlyDeclaredBytesAndDigest() throws Exception { - byte[] packageBytes = "verified android package".getBytes(StandardCharsets.UTF_8); - ByteArrayOutputStream output = new ByteArrayOutputStream(); - - T4UpdateVerifier.copyExact( - new ByteArrayInputStream(packageBytes), - output, - packageBytes.length, - sha256(packageBytes) - ); - - assertArrayEquals(packageBytes, output.toByteArray()); - } - - @Test - public void exactStreamRejectsOversizedUndersizedAndAlteredPackages() throws Exception { - byte[] packageBytes = "package".getBytes(StandardCharsets.UTF_8); - - assertThrows( - IllegalStateException.class, - () -> T4UpdateVerifier.copyExact( - new ByteArrayInputStream(packageBytes), - new ByteArrayOutputStream(), - packageBytes.length - 1, - sha256(packageBytes) - ) - ); - assertThrows( - IllegalStateException.class, - () -> T4UpdateVerifier.copyExact( - new ByteArrayInputStream(packageBytes), - new ByteArrayOutputStream(), - packageBytes.length + 1, - sha256(packageBytes) - ) - ); - assertThrows( - IllegalStateException.class, - () -> T4UpdateVerifier.copyExact( - new ByteArrayInputStream(packageBytes), - new ByteArrayOutputStream(), - packageBytes.length, - sha256("different".getBytes(StandardCharsets.UTF_8)) - ) - ); - } - - @Test - public void signerSetRequiresTheSameNonEmptyCertificatesRegardlessOfOrder() throws Exception { - byte[] first = "first certificate".getBytes(StandardCharsets.UTF_8); - byte[] second = "second certificate".getBytes(StandardCharsets.UTF_8); - byte[] other = "other certificate".getBytes(StandardCharsets.UTF_8); - - assertTrue(T4UpdateVerifier.sameSignerSet(Arrays.asList(first, second), Arrays.asList(second, first))); - assertFalse(T4UpdateVerifier.sameSignerSet(Arrays.asList(first, second), Arrays.asList(first, other))); - assertFalse(T4UpdateVerifier.sameSignerSet(Arrays.asList(first, second), Collections.singletonList(first))); - assertFalse(T4UpdateVerifier.sameSignerSet(Collections.emptyList(), Collections.singletonList(first))); - } - - @Test - public void signerTransitionAcceptsVerifiedForwardRotationAndRejectsRollback() throws Exception { - byte[] first = "first certificate".getBytes(StandardCharsets.UTF_8); - byte[] second = "second certificate".getBytes(StandardCharsets.UTF_8); - byte[] third = "third certificate".getBytes(StandardCharsets.UTF_8); - - assertTrue(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(first), - Collections.singletonList(first), - false, - Collections.singletonList(second), - Arrays.asList(first, second), - false - )); - assertTrue(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(second), - Arrays.asList(first, second), - false, - Collections.singletonList(second), - Collections.singletonList(second), - false - )); - assertFalse(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(second), - Arrays.asList(first, second), - false, - Collections.singletonList(first), - Collections.singletonList(first), - false - )); - assertTrue(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(second), - Arrays.asList(first, second), - false, - Collections.singletonList(third), - Arrays.asList(first, second, third), - false - )); - } - - @Test - public void signerTransitionRejectsMalformedOrUnprovenLineage() throws Exception { - byte[] first = "first certificate".getBytes(StandardCharsets.UTF_8); - byte[] second = "second certificate".getBytes(StandardCharsets.UTF_8); - byte[] third = "third certificate".getBytes(StandardCharsets.UTF_8); - - assertFalse(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(first), - Collections.emptyList(), - false, - Collections.singletonList(second), - Arrays.asList(first, second), - false - )); - assertFalse(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(first), - Collections.singletonList(first), - false, - Collections.singletonList(second), - Arrays.asList(second, first), - false - )); - assertFalse(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(first), - Collections.singletonList(first), - false, - Collections.singletonList(second), - Arrays.asList(first, first, second), - false - )); - assertFalse(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(first), - Collections.singletonList(second), - false, - Collections.singletonList(second), - Arrays.asList(first, second), - false - )); - assertFalse(T4UpdateVerifier.isTrustedSignerTransition( - Collections.singletonList(second), - Arrays.asList(first, second), - false, - Collections.singletonList(third), - Arrays.asList(first, third), - false - )); - } - - @Test - public void multiSignerTransitionRequiresTheSameCompleteSignerSet() throws Exception { - byte[] first = "first certificate".getBytes(StandardCharsets.UTF_8); - byte[] second = "second certificate".getBytes(StandardCharsets.UTF_8); - - assertTrue(T4UpdateVerifier.isTrustedSignerTransition( - Arrays.asList(first, second), - Collections.emptyList(), - true, - Arrays.asList(second, first), - Collections.emptyList(), - true - )); - assertFalse(T4UpdateVerifier.isTrustedSignerTransition( - Arrays.asList(first, second), - Collections.emptyList(), - true, - Collections.singletonList(first), - Collections.singletonList(first), - false - )); - } - - @Test - public void versionsAssetsAndRedirectsAreStrict() throws Exception { - assertTrue(T4UpdateVerifier.compareVersions("1.2.4", "1.2.3") > 0); - assertEquals(0, T4UpdateVerifier.compareVersions("1.2.3", "1.2.3")); - assertTrue(T4UpdateVerifier.compareVersions("1.2.3", "2.0.0") < 0); - assertThrows(IllegalArgumentException.class, () -> T4UpdateVerifier.compareVersions("1.2", "1.2.0")); - assertThrows(IllegalArgumentException.class, () -> T4UpdateVerifier.compareVersions("01.2.3", "1.2.3")); - assertThrows(IllegalArgumentException.class, () -> T4UpdateVerifier.compareVersions("1.2.3-beta", "1.2.3")); - assertEquals( - "T4-Code-1.2.3-android.apk", - T4UpdateVerifier.expectedAssetName("1.2.3", "android", "apk", "universal") - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.expectedAssetName("1.2.3", "android", "aab", "universal") - ); - - T4UpdateVerifier.requireAllowedAssetUrl( - new URL("https://github.com/LycaonLLC/t4-code/releases/download/v1.2.3/T4-Code-1.2.3-android.apk"), - true - ); - T4UpdateVerifier.requireAllowedAssetUrl( - new URL("https://release-assets.githubusercontent.com/github-production-release-asset/file?token=signed"), - false - ); - T4UpdateVerifier.requireAllowedAssetUrl( - new URL("https://objects.githubusercontent.com/github-production-release-asset/file"), - false - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireAllowedAssetUrl(new URL("https://example.com/update.apk"), false) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireAllowedAssetUrl(new URL("http://github.com/update.apk"), true) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireAllowedAssetUrl(new URL("https://github.com.evil.example/update.apk"), true) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireAllowedAssetUrl(new URL("https://user@github.com/update.apk"), true) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireAllowedAssetUrl(new URL("https://github.com:444/update.apk"), true) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireAllowedAssetUrl( - new URL("https://release-assets.githubusercontent.com/update.apk"), - true - ) - ); - assertTrue(T4UpdateVerifier.isRedirectStatus(302)); - assertTrue(T4UpdateVerifier.isRedirectStatus(308)); - assertFalse(T4UpdateVerifier.isRedirectStatus(200)); - } - - @Test - public void manifestIdentityAndEveryPublishedAssetAreExact() { - String version = "1.2.3"; - String digest = String.join("", Collections.nCopies(64, "a")); - T4UpdateVerifier.requireManifestReleaseIdentity( - version, - "v1.2.3", - "https://github.com/LycaonLLC/t4-code/releases/tag/v1.2.3", - "2026-07-15T12:30:00.000Z" - ); - - String[][] assets = new String[][] { - { "android", "apk", "universal", "T4-Code-1.2.3-android.apk" }, - { "linux", "deb", "x86_64", "T4-Code-1.2.3-linux-amd64.deb" }, - { "linux", "appimage", "x86_64", "T4-Code-1.2.3-linux-x86_64.AppImage" }, - { "mac", "dmg", "arm64", "T4-Code-1.2.3-mac-arm64.dmg" }, - { "mac", "zip", "arm64", "T4-Code-1.2.3-mac-arm64.zip" }, - }; - for (String[] asset : assets) { - assertEquals( - asset[0] + ":" + asset[1] + ":" + asset[2], - T4UpdateVerifier.requireManifestAsset( - version, - asset[0], - asset[1], - asset[2], - asset[3], - "https://github.com/LycaonLLC/t4-code/releases/download/v1.2.3/" + asset[3], - 1024, - digest, - 2048 - ) - ); - } - - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireManifestReleaseIdentity( - version, - "v1.2.4", - "https://github.com/LycaonLLC/t4-code/releases/tag/v1.2.3", - "2026-07-15T12:30:00Z" - ) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireManifestReleaseIdentity( - version, - "v1.2.3", - "https://example.com/v1.2.3", - "not-a-timestamp" - ) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireManifestAsset( - version, - "android", - "apk", - "universal", - "T4-Code-1.2.3-android.apk", - "https://example.com/T4-Code-1.2.3-android.apk", - 1024, - digest, - 2048 - ) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireManifestAsset( - version, - "android", - "apk", - "universal", - "T4-Code-1.2.3-android.apk", - "https://github.com/LycaonLLC/t4-code/releases/download/v1.2.3/T4-Code-1.2.3-android.apk", - 2049, - digest, - 2048 - ) - ); - assertThrows( - IllegalArgumentException.class, - () -> T4UpdateVerifier.requireManifestAsset( - version, - "android", - "apk", - "universal", - "T4-Code-1.2.3-android.apk", - "https://github.com/LycaonLLC/t4-code/releases/download/v1.2.3/T4-Code-1.2.3-android.apk", - 1024, - digest.toUpperCase(java.util.Locale.ROOT), - 2048 - ) - ); - } - - private static String sha256(byte[] input) throws Exception { - byte[] digest = MessageDigest.getInstance("SHA-256").digest(input); - StringBuilder result = new StringBuilder(digest.length * 2); - for (byte item : digest) result.append(String.format(java.util.Locale.ROOT, "%02x", item & 0xff)); - return result.toString(); - } -} diff --git a/apps/mobile/android/build.gradle b/apps/mobile/android/build.gradle deleted file mode 100644 index f8f0e43b..00000000 --- a/apps/mobile/android/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. - -buildscript { - - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:8.13.0' - classpath 'com.google.gms:google-services:4.4.4' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -apply from: "variables.gradle" - -allprojects { - repositories { - google() - mavenCentral() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} diff --git a/apps/mobile/android/capacitor.settings.gradle b/apps/mobile/android/capacitor.settings.gradle deleted file mode 100644 index e0f161b8..00000000 --- a/apps/mobile/android/capacitor.settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -// DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN -include ':capacitor-android' -project(':capacitor-android').projectDir = new File('../../../node_modules/.pnpm/@capacitor+android@8.4.1_@capacitor+core@8.4.1/node_modules/@capacitor/android/capacitor') diff --git a/apps/mobile/android/gradle.properties b/apps/mobile/android/gradle.properties deleted file mode 100644 index 2e87c52f..00000000 --- a/apps/mobile/android/gradle.properties +++ /dev/null @@ -1,22 +0,0 @@ -# Project-wide Gradle settings. - -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. - -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html - -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx1536m - -# When configured, Gradle will run in incubating parallel mode. -# This option should only be used with decoupled projects. More details, visit -# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects -# org.gradle.parallel=true - -# AndroidX package structure to make it clearer which packages are bundled with the -# Android operating system, and which are packaged with your app's APK -# https://developer.android.com/topic/libraries/support-library/androidx-rn -android.useAndroidX=true diff --git a/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar b/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar deleted file mode 100644 index 1b33c55b..00000000 Binary files a/apps/mobile/android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ diff --git a/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties b/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index e62db30c..00000000 --- a/apps/mobile/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,8 +0,0 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-all.zip -distributionSha256Sum=ed1a8d686605fd7c23bdf62c7fc7add1c5b23b2bbc3721e661934ef4a4911d7c -networkTimeout=10000 -validateDistributionUrl=true -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists diff --git a/apps/mobile/android/gradlew b/apps/mobile/android/gradlew deleted file mode 100755 index 23d15a93..00000000 --- a/apps/mobile/android/gradlew +++ /dev/null @@ -1,251 +0,0 @@ -#!/bin/sh - -# -# Copyright © 2015-2021 the original authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -# SPDX-License-Identifier: Apache-2.0 -# - -############################################################################## -# -# Gradle start up script for POSIX generated by Gradle. -# -# Important for running: -# -# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is -# noncompliant, but you have some other compliant shell such as ksh or -# bash, then to run this script, type that shell name before the whole -# command line, like: -# -# ksh Gradle -# -# Busybox and similar reduced shells will NOT work, because this script -# requires all of these POSIX shell features: -# * functions; -# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», -# «${var#prefix}», «${var%suffix}», and «$( cmd )»; -# * compound commands having a testable exit status, especially «case»; -# * various built-in commands including «command», «set», and «ulimit». -# -# Important for patching: -# -# (2) This script targets any POSIX shell, so it avoids extensions provided -# by Bash, Ksh, etc; in particular arrays are avoided. -# -# The "traditional" practice of packing multiple parameters into a -# space-separated string is a well documented source of bugs and security -# problems, so this is (mostly) avoided, by progressively accumulating -# options in "$@", and eventually passing that to Java. -# -# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, -# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; -# see the in-line comments for details. -# -# There are tweaks for specific operating systems such as AIX, CygWin, -# Darwin, MinGW, and NonStop. -# -# (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt -# within the Gradle project. -# -# You can find Gradle at https://github.com/gradle/gradle/. -# -############################################################################## - -# Attempt to set APP_HOME - -# Resolve links: $0 may be a link -app_path=$0 - -# Need this for daisy-chained symlinks. -while - APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path - [ -h "$app_path" ] -do - ls=$( ls -ld "$app_path" ) - link=${ls#*' -> '} - case $link in #( - /*) app_path=$link ;; #( - *) app_path=$APP_HOME$link ;; - esac -done - -# This is normally unused -# shellcheck disable=SC2034 -APP_BASE_NAME=${0##*/} -# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit - -# Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD=maximum - -warn () { - echo "$*" -} >&2 - -die () { - echo - echo "$*" - echo - exit 1 -} >&2 - -# OS specific support (must be 'true' or 'false'). -cygwin=false -msys=false -darwin=false -nonstop=false -case "$( uname )" in #( - CYGWIN* ) cygwin=true ;; #( - Darwin* ) darwin=true ;; #( - MSYS* | MINGW* ) msys=true ;; #( - NONSTOP* ) nonstop=true ;; -esac - -CLASSPATH="\\\"\\\"" - - -# Determine the Java command to use to start the JVM. -if [ -n "$JAVA_HOME" ] ; then - if [ -x "$JAVA_HOME/jre/sh/java" ] ; then - # IBM's JDK on AIX uses strange locations for the executables - JAVACMD=$JAVA_HOME/jre/sh/java - else - JAVACMD=$JAVA_HOME/bin/java - fi - if [ ! -x "$JAVACMD" ] ; then - die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -else - JAVACMD=java - if ! command -v java >/dev/null 2>&1 - then - die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. - -Please set the JAVA_HOME variable in your environment to match the -location of your Java installation." - fi -fi - -# Increase the maximum file descriptors if we can. -if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then - case $MAX_FD in #( - max*) - # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - MAX_FD=$( ulimit -H -n ) || - warn "Could not query maximum file descriptor limit" - esac - case $MAX_FD in #( - '' | soft) :;; #( - *) - # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC2039,SC3045 - ulimit -n "$MAX_FD" || - warn "Could not set maximum file descriptor limit to $MAX_FD" - esac -fi - -# Collect all arguments for the java command, stacking in reverse order: -# * args from the command line -# * the main class name -# * -classpath -# * -D...appname settings -# * --module-path (only if needed) -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. - -# For Cygwin or MSYS, switch paths to Windows format before running java -if "$cygwin" || "$msys" ; then - APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) - - JAVACMD=$( cygpath --unix "$JAVACMD" ) - - # Now convert the arguments - kludge to limit ourselves to /bin/sh - for arg do - if - case $arg in #( - -*) false ;; # don't mess with options #( - /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath - [ -e "$t" ] ;; #( - *) false ;; - esac - then - arg=$( cygpath --path --ignore --mixed "$arg" ) - fi - # Roll the args list around exactly as many times as the number of - # args, so each arg winds up back in the position where it started, but - # possibly modified. - # - # NB: a `for` loop captures its iteration list before it begins, so - # changing the positional parameters here affects neither the number of - # iterations, nor the values presented in `arg`. - shift # remove old arg - set -- "$@" "$arg" # push replacement arg - done -fi - - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' - -# Collect all arguments for the java command: -# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, -# and any embedded shellness will be escaped. -# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be -# treated as '${Hostname}' itself on the command line. - -set -- \ - "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ - -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ - "$@" - -# Stop when "xargs" is not available. -if ! command -v xargs >/dev/null 2>&1 -then - die "xargs is not available" -fi - -# Use "xargs" to parse quoted args. -# -# With -n1 it outputs one arg per line, with the quotes and backslashes removed. -# -# In Bash we could simply go: -# -# readarray ARGS < <( xargs -n1 <<<"$var" ) && -# set -- "${ARGS[@]}" "$@" -# -# but POSIX shell has neither arrays nor command substitution, so instead we -# post-process each arg (as a line of input to sed) to backslash-escape any -# character that might be a shell metacharacter, then use eval to reverse -# that process (while maintaining the separation between arguments), and wrap -# the whole thing up as a single "set" statement. -# -# This will of course break if any of these variables contains a newline or -# an unmatched quote. -# - -eval "set -- $( - printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | - xargs -n1 | - sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | - tr '\n' ' ' - )" '"$@"' - -exec "$JAVACMD" "$@" diff --git a/apps/mobile/android/gradlew.bat b/apps/mobile/android/gradlew.bat deleted file mode 100644 index 5eed7ee8..00000000 --- a/apps/mobile/android/gradlew.bat +++ /dev/null @@ -1,94 +0,0 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem -@rem SPDX-License-Identifier: Apache-2.0 -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. 1>&2 -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 -echo. 1>&2 -echo Please set the JAVA_HOME variable in your environment to match the 1>&2 -echo location of your Java installation. 1>&2 - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH= - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega diff --git a/apps/mobile/android/settings.gradle b/apps/mobile/android/settings.gradle deleted file mode 100644 index 3b4431d7..00000000 --- a/apps/mobile/android/settings.gradle +++ /dev/null @@ -1,5 +0,0 @@ -include ':app' -include ':capacitor-cordova-android-plugins' -project(':capacitor-cordova-android-plugins').projectDir = new File('./capacitor-cordova-android-plugins/') - -apply from: 'capacitor.settings.gradle' \ No newline at end of file diff --git a/apps/mobile/android/variables.gradle b/apps/mobile/android/variables.gradle deleted file mode 100644 index ee4ba41c..00000000 --- a/apps/mobile/android/variables.gradle +++ /dev/null @@ -1,16 +0,0 @@ -ext { - minSdkVersion = 24 - compileSdkVersion = 36 - targetSdkVersion = 36 - androidxActivityVersion = '1.11.0' - androidxAppCompatVersion = '1.7.1' - androidxCoordinatorLayoutVersion = '1.3.0' - androidxCoreVersion = '1.17.0' - androidxFragmentVersion = '1.8.9' - coreSplashScreenVersion = '1.2.0' - androidxWebkitVersion = '1.14.0' - junitVersion = '4.13.2' - androidxJunitVersion = '1.3.0' - androidxEspressoCoreVersion = '3.7.0' - cordovaAndroidVersion = '14.0.1' -} \ No newline at end of file diff --git a/apps/mobile/assets/android-icon-foreground.svg b/apps/mobile/assets/android-icon-foreground.svg deleted file mode 100644 index 4f297d5f..00000000 --- a/apps/mobile/assets/android-icon-foreground.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/apps/mobile/capacitor.config.json b/apps/mobile/capacitor.config.json deleted file mode 100644 index a78f508b..00000000 --- a/apps/mobile/capacitor.config.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "appId": "com.lycaonsolutions.t4code", - "appName": "T4 Code", - "webDir": "dist", - "loggingBehavior": "debug", - "appendUserAgent": " T4CodeMobile/0.1.29", - "android": { - "path": "android", - "minWebViewVersion": 60, - "allowMixedContent": false, - "webContentsDebuggingEnabled": false - }, - "plugins": { - "SystemBars": { - "insetsHandling": "css" - } - }, - "server": { - "hostname": "localhost", - "androidScheme": "https" - } -} diff --git a/apps/mobile/package.json b/apps/mobile/package.json deleted file mode 100644 index 8a75bfbb..00000000 --- a/apps/mobile/package.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "name": "@t4-code/mobile", - "version": "0.1.29", - "private": true, - "type": "module", - "scripts": { - "prepare:web": "node ./scripts/prepare-web.mjs", - "sync:android": "pnpm prepare:web && cap sync android", - "build:android:debug": "pnpm sync:android && node ./scripts/run-gradle.mjs assembleDebug", - "check:android:debug": "pnpm sync:android && node ./scripts/run-gradle.mjs testDebugUnitTest assembleDebug lintDebug", - "build:android:release": "pnpm sync:android && node ./scripts/run-gradle.mjs assembleRelease", - "test": "node --test ./scripts/*.test.mjs" - }, - "dependencies": { - "@capacitor/core": "8.4.1" - }, - "devDependencies": { - "@capacitor/android": "8.4.1", - "@capacitor/cli": "8.4.1" - } -} diff --git a/apps/mobile/scripts/prepare-web.mjs b/apps/mobile/scripts/prepare-web.mjs deleted file mode 100644 index b00081cd..00000000 --- a/apps/mobile/scripts/prepare-web.mjs +++ /dev/null @@ -1,56 +0,0 @@ -import { cp, mkdir, readFile, rm, writeFile } from "node:fs/promises"; -import { dirname, resolve } from "node:path"; -import { fileURLToPath } from "node:url"; - -const mobileRoot = resolve(dirname(fileURLToPath(import.meta.url)), ".."); -const webDist = resolve(mobileRoot, "../web/dist"); -const mobileDist = resolve(mobileRoot, "dist"); -const sourceIndex = resolve(webDist, "index.html"); -const MOBILE_CONTENT_SECURITY_POLICY = [ - "default-src 'self'", - "script-src 'self'", - "style-src 'self' 'unsafe-inline'", - "font-src 'self' data:", - "img-src 'self' data: blob:", - "connect-src 'self' wss://*.ts.net:*", - "object-src 'none'", - "base-uri 'none'", - "frame-src 'none'", - "form-action 'none'", -].join("; "); - -let indexHtml; -try { - indexHtml = await readFile(sourceIndex, "utf8"); -} catch { - throw new Error("apps/web/dist is missing; run `pnpm build:web` before syncing the mobile shell"); -} - -if (!indexHtml.includes('
')) { - throw new Error("apps/web/dist/index.html is not a T4 Code web build"); -} -if (indexHtml.includes('http-equiv="Content-Security-Policy"')) { - throw new Error("apps/web/dist/index.html already contains a content security policy"); -} - -const mobileIndexHtml = indexHtml.replace( - " ", - ` \n `, -); -if (mobileIndexHtml === indexHtml) { - throw new Error("apps/web/dist/index.html is missing its closing head element"); -} - -await rm(mobileDist, { force: true, recursive: true }); -await mkdir(mobileDist, { recursive: true }); -await cp(webDist, mobileDist, { recursive: true }); -await writeFile(resolve(mobileDist, "index.html"), mobileIndexHtml, "utf8"); - -const buildMetadata = { - appId: "com.lycaonsolutions.t4code", - bundledWebApp: true, - nativeOrigin: "https://localhost", -}; -await writeFile(resolve(mobileDist, "mobile-build.json"), `${JSON.stringify(buildMetadata, null, 2)}\n`, "utf8"); - -console.log(`Prepared bundled T4 web assets in ${mobileDist}`); diff --git a/apps/mobile/scripts/prepare-web.test.mjs b/apps/mobile/scripts/prepare-web.test.mjs deleted file mode 100644 index a36ce726..00000000 --- a/apps/mobile/scripts/prepare-web.test.mjs +++ /dev/null @@ -1,197 +0,0 @@ -import assert from "node:assert/strict"; -import { readFile } from "node:fs/promises"; -import { resolve } from "node:path"; -import test from "node:test"; - -const mobileRoot = resolve(import.meta.dirname, ".."); - -test("Capacitor uses bundled assets and a local secure origin", async () => { - const config = JSON.parse(await readFile(resolve(mobileRoot, "capacitor.config.json"), "utf8")); - - assert.equal(config.webDir, "dist"); - assert.equal(config.server.hostname, "localhost"); - assert.equal(config.server.androidScheme, "https"); - assert.equal(config.server.url, undefined); - assert.equal(config.server.allowNavigation, undefined); - assert.equal(config.server.cleartext, undefined); -}); - -test("Android system bars hand insets to CSS through the SystemBars plugin", async () => { - const config = JSON.parse(await readFile(resolve(mobileRoot, "capacitor.config.json"), "utf8")); - const hostedIndex = await readFile(resolve(mobileRoot, "../web/index.html"), "utf8"); - const systemBars = await readFile( - resolve( - mobileRoot, - "node_modules/@capacitor/android/capacitor/src/main/java/com/getcapacitor/plugin/SystemBars.java", - ), - "utf8", - ); - - // Capacitor 8 core registers SystemBars itself; the config keeps the CSS - // variable injection (--safe-area-inset-*) explicit so the web token layer - // always has a value on Android WebViews where env() misreports. - assert.equal(config.plugins.SystemBars.insetsHandling, "css"); - assert.match(hostedIndex, /viewport-fit=cover/); - assert.match(systemBars, /--safe-area-inset-top/); - assert.match(systemBars, /--safe-area-inset-bottom/); -}); - -test("mobile package pins one Capacitor release across core, CLI, and Android", async () => { - const packageJson = JSON.parse(await readFile(resolve(mobileRoot, "package.json"), "utf8")); - const core = packageJson.dependencies["@capacitor/core"]; - - assert.equal(core, "8.4.1"); - assert.equal(packageJson.devDependencies["@capacitor/cli"], core); - assert.equal(packageJson.devDependencies["@capacitor/android"], core); -}); - -test("Android credentials are encrypted by a registered Keystore plugin", async () => { - const sourceRoot = resolve( - mobileRoot, - "android/app/src/main/java/com/lycaonsolutions/t4code", - ); - const activity = await readFile(resolve(sourceRoot, "MainActivity.java"), "utf8"); - const plugin = await readFile(resolve(sourceRoot, "T4SecureStoragePlugin.java"), "utf8"); - - assert.match(activity, /registerPlugin\(T4SecureStoragePlugin\.class\)/); - assert.match(plugin, /@CapacitorPlugin\(name = "T4SecureStorage"\)/); - assert.match(plugin, /AES\/GCM\/NoPadding/); - assert.match(plugin, /AndroidKeyStore/); - assert.match(plugin, /setCredentials\(PluginCall call\)/); - assert.match(plugin, /getCredentials\(PluginCall call\)/); - assert.match(plugin, /clearCredentials\(PluginCall call\)/); - assert.doesNotMatch(plugin, /putString\([^,]+,\s*deviceToken\)/); -}); - -test("Android secure credentials are host-scoped and migrate legacy storage", async () => { - const sourceRoot = resolve( - mobileRoot, - "android/app/src/main/java/com/lycaonsolutions/t4code", - ); - const plugin = await readFile(resolve(sourceRoot, "T4SecureStoragePlugin.java"), "utf8"); - - assert.match(plugin, /call\.getString\("hostKey"\)/); - assert.match(plugin, /MAX_HOST_KEY_LENGTH/); - assert.equal((plugin.match(/String hostKey = call\.getString\("hostKey"\)/g) ?? []).length, 3); - assert.equal((plugin.match(/isBoundedText\(hostKey, MAX_HOST_KEY_LENGTH\)/g) ?? []).length, 3); - assert.match(plugin, /call\.getBoolean\("migrateLegacy", false\)/); - assert.match(plugin, /readCredentials\(hostKey, migrateLegacy\)/); - assert.match(plugin, /if \(!migrateLegacy\) return null/); - assert.match(plugin, /MessageDigest\.getInstance\("SHA-256"\)/); - assert.match(plugin, /PREFERENCE_IV_PREFIX/); - assert.match(plugin, /PREFERENCE_PAYLOAD_PREFIX/); - assert.match(plugin, /preferenceIv\(hostKey\)/); - assert.match(plugin, /preferencePayload\(hostKey\)/); - assert.match(plugin, /cipher\.updateAAD\(hostKey\.getBytes\(StandardCharsets\.UTF_8\)\)/); - assert.match(plugin, /putString\(preferenceIv\(hostKey\), Base64\.encodeToString/); - assert.match(plugin, /putString\(preferencePayload\(hostKey\), Base64\.encodeToString/); - assert.match(plugin, /decryptCredentials\(legacyIv, legacyPayload, null\)/); - assert.match(plugin, /storeCredentials\(hostKey, credentials\.toString\(\), true\)/); - assert.match(plugin, /SharedPreferences\.Editor editor = preferences\(\)\.edit\(\)/); - assert.match(plugin, /if \(removeLegacy\)/); - assert.match(plugin, /PREFERENCE_IV = "credentials_iv"/); - assert.match(plugin, /PREFERENCE_PAYLOAD = "credentials_payload"/); - assert.match(plugin, /remove\(PREFERENCE_IV\)/); - assert.doesNotMatch(plugin, /storedPreferences\.edit\(\)\s*\.remove\(PREFERENCE_IV\)/); - assert.match(plugin, /remove\(PREFERENCE_PAYLOAD\)/); - assert.match(plugin, /clearStoredState\(hostKey\)/); - assert.doesNotMatch(plugin, /preferences\(\)\.edit\(\)\.clear\(\)/); - assert.doesNotMatch(plugin, /keyStore\.deleteEntry\(KEY_ALIAS\)/); - assert.doesNotMatch(plugin, /putString\([^)]*deviceToken/); - assert.doesNotMatch(plugin, /putString\([^)]*"deviceToken"/); -}); - -test("Android foreground resume wakes the browser connection immediately", async () => { - const activity = await readFile( - resolve( - mobileRoot, - "android/app/src/main/java/com/lycaonsolutions/t4code/MainActivity.java", - ), - "utf8", - ); - - assert.match(activity, /void onResume\(\)/); - assert.match(activity, /super\.onResume\(\)/); - assert.match(activity, /triggerWindowJSEvent\(APP_RESUME_EVENT\)/); - assert.match(activity, /APP_RESUME_EVENT = "t4:native-resume"/); -}); - -test("Android updates use a registered native bridge with no renderer-supplied URL", async () => { - const sourceRoot = resolve( - mobileRoot, - "android/app/src/main/java/com/lycaonsolutions/t4code", - ); - const activity = await readFile(resolve(sourceRoot, "MainActivity.java"), "utf8"); - const fileProvider = await readFile(resolve(sourceRoot, "T4FileProvider.java"), "utf8"); - const fileStore = await readFile(resolve(sourceRoot, "T4UpdateFileStore.java"), "utf8"); - const plugin = await readFile(resolve(sourceRoot, "T4UpdatePlugin.java"), "utf8"); - const verifier = await readFile(resolve(sourceRoot, "T4UpdateVerifier.java"), "utf8"); - const manifest = await readFile(resolve(mobileRoot, "android/app/src/main/AndroidManifest.xml"), "utf8"); - const providerPaths = await readFile( - resolve(mobileRoot, "android/app/src/main/res/xml/file_paths.xml"), - "utf8", - ); - - assert.match(activity, /registerPlugin\(T4UpdatePlugin\.class\)/); - assert.match(plugin, /@CapacitorPlugin\(name = "T4Update"\)/); - assert.match(plugin, /https:\/\/t4code\.net\/releases\/latest\.json/); - assert.match(verifier, /https:\/\/github\.com\/LycaonLLC\/t4-code\/releases\/download\//); - assert.match(plugin, /checkForUpdate\(PluginCall call\)/); - assert.match(plugin, /openUpdate\(PluginCall call\)/); - assert.match(plugin, /T4UpdateVerifier\.copyExact\(input, output, release\.apkSize, release\.apkSha256\)/); - assert.match(plugin, /EXPECTED_PACKAGE_ID = "com\.lycaonsolutions\.t4code"/); - assert.match(plugin, /expectedVersion\.equals\(candidate\.versionName\)/); - assert.match(plugin, /PackageManager\.GET_SIGNING_CERTIFICATES/); - assert.match(plugin, /PackageManager\.GET_SIGNATURES/); - assert.match(plugin, /getSigningCertificateHistory\(\)/); - assert.match(plugin, /T4UpdateVerifier\.isTrustedSignerTransition\(/); - assert.match(plugin, /T4UpdateVerifier\.sameSignerSet\(legacySigners\(installed\), legacySigners\(candidate\)\)/); - assert.match(fileStore, /File\.createTempFile\("T4-Code-" \+ version/); - assert.match(fileStore, /verified\.setReadOnly\(\)/); - assert.match(fileStore, /ACTIVE_OWNERS\.put\(ownershipKey, ownerToken\)/); - assert.match(fileStore, /requireOwnership\(\)/); - assert.match(plugin, /updateState\.installerOpened\(\)/); - assert.match(plugin, /notifyListeners\(STATE_CHANGED_EVENT, state\)/); - assert.match(plugin, /result\.put\("revision", updateState\.revision\(\)\)/); - assert.match(plugin, /BuildConfig\.APPLICATION_ID/); - assert.match(plugin, /BuildConfig\.VERSION_NAME/); - assert.match(plugin, /FileProvider\.getUriForFile/); - assert.match(plugin, /new Intent\(Intent\.ACTION_VIEW\)/); - assert.match(plugin, /Intent\.FLAG_GRANT_READ_URI_PERMISSION/); - assert.match(verifier, /count > expectedSize - total/); - assert.match(verifier, /total != expectedSize/); - assert.match(verifier, /MessageDigest\.getInstance\("SHA-256"\)/); - assert.match(manifest, /android\.permission\.REQUEST_INSTALL_PACKAGES/); - assert.match(fileProvider, /final class T4FileProvider extends FileProvider/); - assert.match(manifest, /android:name="\.T4FileProvider"/); - assert.match(providerPaths, //); - assert.doesNotMatch(providerPaths, / { - const prepareScript = await readFile(resolve(mobileRoot, "scripts/prepare-web.mjs"), "utf8"); - const hostedIndex = await readFile(resolve(mobileRoot, "../web/index.html"), "utf8"); - - assert.match(prepareScript, /connect-src 'self' wss:\/\/\*\.ts\.net:\*/); - assert.match(prepareScript, /img-src 'self' data: blob:/); - assert.match(prepareScript, /http-equiv="Content-Security-Policy"/); - assert.doesNotMatch(prepareScript, /connect-src \*/); - assert.doesNotMatch(hostedIndex, /http-equiv="Content-Security-Policy"/); -}); - -test("the Android build verifies its Gradle distribution", async () => { - const wrapper = await readFile( - resolve(mobileRoot, "android/gradle/wrapper/gradle-wrapper.properties"), - "utf8", - ); - - assert.match(wrapper, /^distributionSha256Sum=[a-f0-9]{64}$/mu); - assert.match(wrapper, /^validateDistributionUrl=true$/mu); -}); diff --git a/apps/mobile/scripts/run-gradle.mjs b/apps/mobile/scripts/run-gradle.mjs deleted file mode 100644 index 72d806b1..00000000 --- a/apps/mobile/scripts/run-gradle.mjs +++ /dev/null @@ -1,39 +0,0 @@ -import { spawn } from "node:child_process"; -import { access } from "node:fs/promises"; -import { delimiter, resolve } from "node:path"; - -const mobileRoot = resolve(import.meta.dirname, ".."); -const gradlew = resolve(mobileRoot, "android/gradlew"); -const sdkRoot = process.env.ANDROID_HOME ?? process.env.ANDROID_SDK_ROOT ?? resolve(process.env.HOME ?? "", "Android/sdk"); -const javaHome = process.env.JAVA_HOME ?? "/usr/lib/jvm/java-21-openjdk-amd64"; - -await access(gradlew); -await access(resolve(javaHome, "bin/java")); - -const tasks = process.argv.slice(2); -if (tasks.length === 0) throw new Error("pass at least one Gradle task"); - -const child = spawn(gradlew, ["--no-daemon", ...tasks], { - cwd: resolve(mobileRoot, "android"), - env: { - ...process.env, - ANDROID_HOME: sdkRoot, - ANDROID_SDK_ROOT: sdkRoot, - JAVA_HOME: javaHome, - PATH: `${resolve(javaHome, "bin")}${delimiter}${resolve(sdkRoot, "platform-tools")}${delimiter}${process.env.PATH ?? ""}`, - }, - stdio: "inherit", -}); - -child.on("error", (error) => { - throw error; -}); - -const exitCode = await new Promise((resolveExit) => { - child.on("exit", (code, signal) => { - if (signal !== null) throw new Error(`Gradle terminated by ${signal}`); - resolveExit(code ?? 1); - }); -}); - -process.exitCode = exitCode; diff --git a/apps/web/src/features/targets/TargetsScreen.tsx b/apps/web/src/features/targets/TargetsScreen.tsx index 45bc4d6c..60079fbe 100644 --- a/apps/web/src/features/targets/TargetsScreen.tsx +++ b/apps/web/src/features/targets/TargetsScreen.tsx @@ -35,6 +35,7 @@ import { type TargetCapabilityGroupId, type TargetRow, } from "./model.ts"; +import { companionSetupLink } from "./phone-setup.ts"; import { useTargets, type ProfileActionId, @@ -189,6 +190,7 @@ function PhoneSetupCard({ api }: { readonly api: PhoneSetupApi }) { const [state, setState] = useState(null); const [busy, setBusy] = useState(false); const [copied, setCopied] = useState(false); + const [destination, setDestination] = useState<"companion" | "browser">("companion"); useEffect(() => { let active = true; void inspect().then((next) => { if (active) setState(next); }, () => { @@ -204,12 +206,16 @@ function PhoneSetupCard({ api }: { readonly api: PhoneSetupApi }) { }; const copy = async () => { if (!state?.url) return; - try { await navigator.clipboard.writeText(state.url); } + const setupUrl = destination === "companion" ? companionSetupLink(state.url) : state.url; + try { await navigator.clipboard.writeText(setupUrl); } catch { return; } setCopied(true); window.setTimeout(() => setCopied(false), 1_500); }; const ready = state?.phase === "ready" && state.url !== undefined; + const setupUrl = ready + ? destination === "companion" ? companionSetupLink(state.url!) : state.url! + : undefined; return (
@@ -227,21 +233,39 @@ function PhoneSetupCard({ api }: { readonly api: PhoneSetupApi }) { {ready ? (
- +
-
    -
  1. Open Tailscale on your phone, sign in to the same account, and connect.
  2. -
  3. - For automatic reconnect, tap your Tailscale profile picture, open - {" "}VPN On Demand, and set both Wi-Fi and - Cellular to Always. -
  4. -
  5. Scan this code with your phone camera.
  6. -
  7. Choose Add to Home Screen in Safari if you want an app icon.
  8. -
+
+ + +
+ {destination === "companion" ? ( +
    +
  1. Open Tailscale on your phone, sign in to the same account, and connect.
  2. +
  3. Install T4 Companion, then scan this code with your phone camera.
  4. +
  5. Choose Open in T4 Companion.
  6. +
  7. The app saves this Mac and reconnects automatically from then on.
  8. +
+ ) : ( +
    +
  1. Open Tailscale on your phone, sign in to the same account, and connect.
  2. +
  3. Scan this code with your phone camera.
  4. +
  5. In Safari, choose Add to Home Screen.
  6. +
  7. This address stays the same as long as the Mac keeps its Tailscale name.
  8. +
+ )} +

+ For automatic reconnect, open your Tailscale profile settings, choose + {" "}VPN On Demand, and set both Wi-Fi and Cellular to + {" "}Always. +

- {state.url} + {setupUrl} diff --git a/apps/web/src/features/targets/phone-setup.ts b/apps/web/src/features/targets/phone-setup.ts new file mode 100644 index 00000000..f69e5c4c --- /dev/null +++ b/apps/web/src/features/targets/phone-setup.ts @@ -0,0 +1,20 @@ +export function companionSetupLink(address: string, profile = "default"): string { + const host = new URL(address); + if ( + host.protocol !== "https:" + || host.username !== "" + || host.password !== "" + || host.pathname !== "/" + || host.search !== "" + || host.hash !== "" + ) { + throw new Error("Companion setup requires a root HTTPS host address."); + } + const normalizedProfile = profile.trim() || "default"; + if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/u.test(normalizedProfile)) { + throw new Error("Companion setup profile is invalid."); + } + const query = new URLSearchParams({ address: host.origin }); + if (normalizedProfile !== "default") query.set("profile", normalizedProfile); + return `t4companion://?${query.toString()}`; +} diff --git a/apps/web/test/phone-setup-link.test.ts b/apps/web/test/phone-setup-link.test.ts new file mode 100644 index 00000000..d6db30de --- /dev/null +++ b/apps/web/test/phone-setup-link.test.ts @@ -0,0 +1,19 @@ +import { describe, expect, it } from "vite-plus/test"; + +import { companionSetupLink } from "../src/features/targets/phone-setup.ts"; + +describe("companion phone setup link", () => { + it("encodes the stable Tailnet origin for one-time native app setup", () => { + expect(companionSetupLink("https://mac.example.ts.net:8445")).toBe( + "t4companion://?address=https%3A%2F%2Fmac.example.ts.net%3A8445", + ); + }); + + it("includes a non-default profile and rejects unsafe host URLs", () => { + expect(companionSetupLink("https://mac.example.ts.net", "work")).toBe( + "t4companion://?address=https%3A%2F%2Fmac.example.ts.net&profile=work", + ); + expect(() => companionSetupLink("http://mac.example.ts.net")).toThrow(/root HTTPS/u); + expect(() => companionSetupLink("https://mac.example.ts.net/private")).toThrow(/root HTTPS/u); + }); +}); diff --git a/docs/TAILNET_REMOTE.md b/docs/TAILNET_REMOTE.md index 6d762fd2..78faa292 100644 --- a/docs/TAILNET_REMOTE.md +++ b/docs/TAILNET_REMOTE.md @@ -12,8 +12,8 @@ phone browser or bundled T4 mobile UI There is no T4 password in this mode. Tailscale membership and your tailnet ACLs or grants are the access boundary. The gateway never listens on a LAN -interface. It accepts WebSocket connections from the exact configured `.ts.net` -HTTPS origin and the two fixed Capacitor WebView origins described below. No +interface. It accepts WebSocket connections only from the exact shared +`.ts.net` HTTPS origin configured for both the browser and Expo companion. No token or password is passed through the browser or mobile UI. The Apple Silicon Mac package contains the gateway. Open **Settings → Hosts**, @@ -152,31 +152,20 @@ connected to the tailnet, the host gateway must be running, and OMP remains the owner of every session. The web app deliberately does not cache transcripts, host configuration, or runtime state for offline use. -## Native mobile client origins +## Native mobile client origin -The Android APK and the planned iOS build bundle the T4 UI. They connect to the -same `/v1/ws` endpoint over WSS; they do not load the hosted website into the -WebView. Capacitor's documented defaults give those bundled pages these -origins: - -- Android: `https://localhost` -- iOS: `capacitor://localhost` - -The service installer writes those exact values to -`T4_NATIVE_ALLOWED_ORIGINS`. The gateway accepts them in addition to the one -Tailnet HTTPS origin passed through `--origin`. Empty values, `null`, wildcard -origins, alternate localhost schemes, and extra origins are rejected at -startup. These values must stay aligned with the mobile -`server.hostname`, `server.androidScheme`, and `server.iosScheme` settings. See -the [Capacitor configuration reference](https://capacitorjs.com/docs/config#schema) -for the platform defaults. +The native Expo clients for Android and iOS connect directly to the same +`/v1/ws` endpoint over WSS; they do not embed the hosted website. The client +sets its WebSocket `Origin` to the endpoint's exact HTTPS origin. The gateway +therefore applies one origin policy to the browser and both native clients: the +Tailnet HTTPS origin passed through `--origin`. Alternate hosts, ports, +schemes, `null`, wildcards, and missing origins are rejected. The hosted web response keeps an exact `connect-src` entry for its configured -`wss://HOST.ts.net[:PORT]` endpoint. The bundled mobile document can connect to -user-selected profiles only under `wss://*.ts.net:*`. That subdomain pattern -restricts outbound destinations; it is never added to the gateway's accepted -WebSocket Origin list. Do not replace either policy with an unrestricted -`connect-src *`. +`wss://HOST.ts.net[:PORT]` endpoint. The native companion validates each saved +host through `parseCompanionHost`, which requires HTTPS and a full `.ts.net` +hostname before deriving the WSS route. Do not replace the hosted policy with +an unrestricted `connect-src *` or relax the native host validation. An Origin header identifies the page that opened the WebSocket. It does not prove that the caller is the signed T4 APK. Tailscale membership and ACLs remain @@ -274,10 +263,10 @@ removes all of them. - The local HTTP gateway is fixed to `127.0.0.1` (or `::1` when explicitly configured by code); it cannot be configured to listen on a LAN address. -- The hosted browser endpoint is an exact HTTPS `.ts.net` origin. The native - exceptions are exactly `https://localhost` and `capacitor://localhost`. - Plain HTTP Tailnet origins, public domains, URL paths, embedded credentials, - `null`, wildcard origins, and any other native origin are rejected. +- Hosted browser and native Expo WebSocket connections must present the same + exact HTTPS `.ts.net` origin. Plain HTTP Tailnet origins, public domains, URL + paths, embedded credentials, missing origins, `null`, and wildcards are + rejected. - Tailscale Serve terminates HTTPS and keeps the route tailnet-only. Do not substitute Funnel. - Every tailnet identity permitted to reach this node and port can operate the diff --git a/docs/assets/companion/after-ios-connect.png b/docs/assets/companion/after-ios-connect.png new file mode 100644 index 00000000..3acb54d4 Binary files /dev/null and b/docs/assets/companion/after-ios-connect.png differ diff --git a/docs/assets/companion/after-ios-live-inbox.png b/docs/assets/companion/after-ios-live-inbox.png new file mode 100644 index 00000000..73c17017 Binary files /dev/null and b/docs/assets/companion/after-ios-live-inbox.png differ diff --git a/docs/assets/companion/after-ios-live-sessions.png b/docs/assets/companion/after-ios-live-sessions.png new file mode 100644 index 00000000..a9db9d5b Binary files /dev/null and b/docs/assets/companion/after-ios-live-sessions.png differ diff --git a/docs/assets/companion/ios-companion-target.png b/docs/assets/companion/ios-companion-target.png new file mode 100644 index 00000000..7e593302 Binary files /dev/null and b/docs/assets/companion/ios-companion-target.png differ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7fdff32b..0e95a53b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -73,7 +73,83 @@ importers: version: 4.3.0 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) + + apps/companion: + dependencies: + '@t4-code/client': + specifier: workspace:* + version: link:../../packages/client + '@t4-code/protocol': + specifier: workspace:* + version: link:../../packages/protocol + expo: + specifier: ~57.0.7 + version: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-constants: + specifier: ~57.0.6 + version: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + expo-linking: + specifier: ~57.0.3 + version: 57.0.3(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + expo-notifications: + specifier: ^57.0.6 + version: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-router: + specifier: ~57.0.7 + version: 57.0.7(8d7c44b937c34bc9b52672f8f7e374b1) + expo-secure-store: + specifier: ~57.0.1 + version: 57.0.1(expo@57.0.7) + expo-speech: + specifier: ^57.0.1 + version: 57.0.1(expo@57.0.7) + expo-speech-recognition: + specifier: 56.0.1 + version: 56.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + expo-splash-screen: + specifier: ~57.0.4 + version: 57.0.4(expo@57.0.7)(typescript@6.0.3) + expo-status-bar: + specifier: ~57.0.1 + version: 57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + expo-system-ui: + specifier: ~57.0.1 + version: 57.0.1(expo@57.0.7)(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + react: + specifier: 19.2.3 + version: 19.2.3 + react-dom: + specifier: 19.2.3 + version: 19.2.3(react@19.2.3) + react-native: + specifier: 0.86.0 + version: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + react-native-safe-area-context: + specifier: ~5.7.0 + version: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-native-screens: + specifier: 4.25.2 + version: 4.25.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-native-web: + specifier: ^0.21.2 + version: 0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + devDependencies: + '@types/react': + specifier: ~19.2.2 + version: 19.2.17 + eslint: + specifier: ^9.39.5 + version: 9.39.5(jiti@2.7.0) + eslint-config-expo: + specifier: ~57.0.0 + version: 57.0.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + typescript: + specifier: ~6.0.3 + version: 6.0.3 + vite-plus: + specifier: 'catalog:' + version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) apps/desktop: dependencies: @@ -116,7 +192,7 @@ importers: version: 26.15.6(electron-builder-squirrel-windows@26.15.6) vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) apps/mobile: dependencies: @@ -154,13 +230,13 @@ importers: version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': specifier: ^6.0.0 - version: 6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + version: 6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0) vite: specifier: npm:@voidzero-dev/vite-plus-core@0.2.2 - version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' + version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) apps/web: dependencies: @@ -239,7 +315,7 @@ importers: devDependencies: '@tailwindcss/vite': specifier: ^4.0.0 - version: 4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + version: 4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) '@types/react': specifier: 'catalog:' version: 19.2.17 @@ -248,16 +324,16 @@ importers: version: 19.2.3(@types/react@19.2.17) '@vitejs/plugin-react': specifier: ^6.0.0 - version: 6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + version: 6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0) tailwindcss: specifier: ^4.0.0 version: 4.3.2 vite: specifier: npm:@voidzero-dev/vite-plus-core@0.2.2 - version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' + version: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) packages/client: dependencies: @@ -276,7 +352,7 @@ importers: version: 8.5.13 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) ws: specifier: 8.18.3 version: 8.18.3 @@ -298,7 +374,7 @@ importers: version: 8.5.13 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) packages/protocol: dependencies: @@ -314,7 +390,7 @@ importers: version: 6.0.3 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) packages/remote: dependencies: @@ -327,7 +403,7 @@ importers: version: 24.12.4 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) packages/service-manager: devDependencies: @@ -336,7 +412,7 @@ importers: version: 24.12.4 vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) packages/ui: dependencies: @@ -373,22 +449,398 @@ importers: version: 19.2.6(react@19.2.6) vite-plus: specifier: 'catalog:' - version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) + version: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) packages: + '@adobe/css-tools@4.5.0': + resolution: {integrity: sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==} + '@babel/code-frame@7.29.7': resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.29.7': + resolution: {integrity: sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.29.7': + resolution: {integrity: sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.29.7': + resolution: {integrity: sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.8': + resolution: {integrity: sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.29.7': + resolution: {integrity: sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.29.7': + resolution: {integrity: sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.29.7': + resolution: {integrity: sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.29.7': + resolution: {integrity: sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + resolution: {integrity: sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.29.7': resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.29.7': + resolution: {integrity: sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-proposal-decorators@7.29.7': + resolution: {integrity: sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-export-default-from@7.29.7': + resolution: {integrity: sha512-p+G5BNXDcy3bOXplhY4HybQ1GxH3i2Tppmdm/3epyRu2VgJJZuUlZ61MqRTg582Q7ZLBdP7fePYvsumSEkMxcQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-decorators@7.29.7': + resolution: {integrity: sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-export-default-from@7.29.7': + resolution: {integrity: sha512-foag0BB37ROhdeIX9O8G0jX7hw0UekJc04cHMrYLOnrErsnBKqJGHJ8eDRpoCFZBvEPPygmmtw4qyU97qa4oOw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-flow@7.29.7': + resolution: {integrity: sha512-ajMX6QPcyomotqwpzhkYGxcK2i/us0rs1Qo9QvUpa+Fca0FTmqrzKrctoIYLMxcOhGZldGT/BAVkRGTWBiR8gQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.29.7': + resolution: {integrity: sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.29.7': + resolution: {integrity: sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-arrow-functions@7.29.7': + resolution: {integrity: sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.7': + resolution: {integrity: sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.29.7': + resolution: {integrity: sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.29.7': + resolution: {integrity: sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.29.7': + resolution: {integrity: sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.29.7': + resolution: {integrity: sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.29.7': + resolution: {integrity: sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.29.7': + resolution: {integrity: sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.29.7': + resolution: {integrity: sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-flow-strip-types@7.29.7': + resolution: {integrity: sha512-wRHeUjUjCZnMHmiO5bRgjFLcoEh7JyTdByOW11ahhwNa4V0bmeGEaIvt51yq0zQp2yWIpqfxXXPyUP6GFJZHOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.29.7': + resolution: {integrity: sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.29.7': + resolution: {integrity: sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.29.7': + resolution: {integrity: sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7': + resolution: {integrity: sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7': + resolution: {integrity: sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.29.7': + resolution: {integrity: sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.29.7': + resolution: {integrity: sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.29.7': + resolution: {integrity: sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.29.7': + resolution: {integrity: sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.29.7': + resolution: {integrity: sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.29.7': + resolution: {integrity: sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.29.7': + resolution: {integrity: sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.29.7': + resolution: {integrity: sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.29.7': + resolution: {integrity: sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.29.7': + resolution: {integrity: sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.7': + resolution: {integrity: sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.29.7': + resolution: {integrity: sha512-xmAscdE/AsqRW7vutbPNoUmu/nF5SrLKPs7aoJgEjo35lLKA/Bc0i2rMv/hr1+Y0o1bQCiVtith3u2vdgRL39Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.29.7': + resolution: {integrity: sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.29.7': + resolution: {integrity: sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.29.7': + resolution: {integrity: sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.29.7': + resolution: {integrity: sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.29.7': + resolution: {integrity: sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.7': resolution: {integrity: sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==} engines: {node: '>=6.9.0'} + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + '@base-ui/react@1.6.0': resolution: {integrity: sha512-/jzjTWJYXhRFO45Bev9lc3cHbmjzCMpUqbMZ2AgKy/z25mY9B6shGSNcXcjQar9n5doM0KYW1W8fcFv2jZBuMw==} engines: {node: '>=14.0.0'} @@ -432,6 +884,10 @@ packages: '@capacitor/core@8.4.1': resolution: {integrity: sha512-xqhOGLbTAYeOWK+IDUNSjQJAPapQjRHrIcgk9PYp52or9zFTaaMko31uNi16N6W+CRJ8VrRram6fOYILkBG2Hg==} + '@egjs/hammerjs@2.0.17': + resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} + engines: {node: '>=0.8.0'} + '@electron/asar@3.4.1': resolution: {integrity: sha512-i4/rNPRS84t0vSRa2HorerGRXWyF4vThfHesw0dmcWHp+cspK743UanA0suA5Q5y8kzY2y6YKrvbIUn69BCAiA==} engines: {node: '>=10.12.0'} @@ -472,69 +928,336 @@ packages: engines: {node: '>=14.14'} hasBin: true - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@floating-ui/react-dom@2.1.8': - resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@floating-ui/react@0.27.19': - resolution: {integrity: sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: - react: '>=17.0.0' - react-dom: '>=17.0.0' + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@fontsource-variable/dm-sans@5.2.8': - resolution: {integrity: sha512-AxkvMTvNWgfrmlyjiV05vlHYJa+nRQCf1EfvIrQAPBpFJW0O9VTz7oAFr9S3lvbWdmnFoBk7yFqQL86u64nl2g==} + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@fontsource/jetbrains-mono@5.2.8': - resolution: {integrity: sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==} + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ionic/cli-framework-output@2.2.8': - resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==} - engines: {node: '>=16.0.0'} + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ionic/utils-array@2.1.6': - resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==} - engines: {node: '>=16.0.0'} + '@eslint/eslintrc@3.3.6': + resolution: {integrity: sha512-l2Ul9PrHsPCKcEY/ac7VgFj9D80C7S68sOKc618SyHDPK36s1XcFebXY0iTzUVn4Yq+YbwvSnDmCz9yxjX+QrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ionic/utils-fs@3.1.7': - resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==} - engines: {node: '>=16.0.0'} + '@eslint/js@9.39.5': + resolution: {integrity: sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ionic/utils-object@2.1.6': - resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==} - engines: {node: '>=16.0.0'} + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ionic/utils-process@2.1.12': - resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==} - engines: {node: '>=16.0.0'} + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ionic/utils-stream@3.1.7': - resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==} - engines: {node: '>=16.0.0'} + '@expo-google-fonts/material-symbols@0.4.41': + resolution: {integrity: sha512-BKNSJ6oFGWi+aXu6PF+1EdI43J8Ag+Sm7Ste1SXYmC2eYOJasyjXHkKjgkIieBB1eDZQJSPWuPv421Vg6N5tCQ==} - '@ionic/utils-subprocess@3.0.1': - resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==} - engines: {node: '>=16.0.0'} + '@expo/cli@57.0.9': + resolution: {integrity: sha512-41z9z68SynNXasZOjuT1si5Sq5OKL6SLf40ZjikbtZgDuvBO8HaUsaDzsJ0c1UZJ3N+vMzYCc1JUIDyRkVBjkA==} + hasBin: true + peerDependencies: + expo: '*' + expo-router: '*' + react-native: '*' + peerDependenciesMeta: + expo-router: + optional: true + react-native: + optional: true - '@ionic/utils-terminal@2.3.5': - resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==} - engines: {node: '>=16.0.0'} + '@expo/code-signing-certificates@0.0.6': + resolution: {integrity: sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==} + + '@expo/config-plugins@57.0.5': + resolution: {integrity: sha512-xhUGgzpFWRghDUH98+Wl4RDakYhTsbyMg6aOYiBjRzPO/THH8tKMw3vlksgFYlU2PkiAdABJN3tNPf5qmvOQhA==} + + '@expo/config-types@57.0.2': + resolution: {integrity: sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g==} + + '@expo/config@57.0.5': + resolution: {integrity: sha512-XqveHQzr6PTqHGnv6NVVZ1CFgB/TgR2mKtHsJA/gYS/76pe2cP1yK/O820xGW2RTnDGTmyhOdagmK6khcN46vg==} + + '@expo/devcert@1.2.1': + resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} + + '@expo/devtools@57.0.1': + resolution: {integrity: sha512-GyUf+wFNkbttaX0jR7MZa9bm77U0IrLg6d2AjpxdyoXw/w4abHoXG0oFufwLMgP9zLTd5+Ct4X/ffNUTnlzZgg==} + peerDependencies: + react: '*' + react-native: '*' + peerDependenciesMeta: + react: + optional: true + react-native: + optional: true + + '@expo/dom-webview@57.0.1': + resolution: {integrity: sha512-lAKsME4SAq+8sf56oN0DX5TBYyruupoRxbWbD2xf9RnKY8y6x8eb9LCE5pxSN0qyWdqnp+0wmyWzDkKboThKAw==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + '@expo/env@2.4.2': + resolution: {integrity: sha512-28pqaEqwnmLduZ00Pq9HkSzE5wbj1MTwp5/n8nm8rD8MCjR9eUnVOwmNksPI3Be2ReAPO/DbPn1puy0mvoocsQ==} + engines: {node: '>=20.12.0'} + + '@expo/expo-modules-macros-plugin@0.6.1': + resolution: {integrity: sha512-cpsLZE4rqkc1Y3eZTkxB98jrqY1YXgetmtxFt8q89jBRmk3quRuk1BZo+VcnCSObZardjg99r1k5xijEMONFGA==} + + '@expo/fingerprint@0.20.5': + resolution: {integrity: sha512-XCDfmbkTpTsYVq1xvvUJvXjfFQs2Hj+icQACBrc6BZmA91YPr2H3uw8sUX13d+1ij6E8lgVCtsK9jh3J2cN/SQ==} + hasBin: true + + '@expo/image-utils@0.11.3': + resolution: {integrity: sha512-yMVjkndhXm9mct0uMq+ndxqT6FgAnhucdUfmXuQ6V6uE021GOiYCACO+KZ0MB4vearPSvbWTGfi32QQr2qocfQ==} + + '@expo/inline-modules@0.1.3': + resolution: {integrity: sha512-eHSxWYfgq65mP3Qz8PclVjUkSrDIlGl3va9U7PMcTpGItOvee/i0ZzGinH5A25oARR5ouD64eESBKwtT/CwdHg==} + + '@expo/json-file@11.0.1': + resolution: {integrity: sha512-zxHWj4MKKMAL29ZQSY/Fssx4Thluk40JmuGNaeS078wy/NhlFhnVi+rHHunulE3xJAJ0CM73m8VK2+GkF9eRwQ==} + + '@expo/local-build-cache-provider@57.0.4': + resolution: {integrity: sha512-B/cI73shkLSYBYuFyh+zCbS+WhqJgawWPW4MPdMiNLJKv9RmV4dv1FGjsidiIiG2k4kYKerBDLK4bLbC7qERQQ==} + + '@expo/log-box@57.0.1': + resolution: {integrity: sha512-fuVNHhOerdRWtpq27gD6JTSVYESsfRu+SMdrNCWxW+gFnusS6dGKfx3lKGBZ4ZkMNiLWn8maBHo39YKzJNXFYQ==} + peerDependencies: + '@expo/dom-webview': ^57.0.1 + expo: '*' + react: '*' + react-native: '*' + + '@expo/metro-config@57.0.6': + resolution: {integrity: sha512-liXA9axM3aykAdil4qdHOYKmQqTDdXYkAoT3Eny+SQEo3btERJCbOk8VH48/G0sVbXCj82AbSS8s3XNEQNqbDQ==} + peerDependencies: + expo: '*' + peerDependenciesMeta: + expo: + optional: true + + '@expo/metro-file-map@57.0.1': + resolution: {integrity: sha512-8JXfVstZN7QnP4NianZZnlTVboOWR0sG8trUDNajOjnbGlPln29vponXM84tY+3tAHapz5/TxE53L0ixUwqPtA==} + + '@expo/metro-runtime@57.0.6': + resolution: {integrity: sha512-x/lAtmPBkZGI3YrGirKQ77Ybmd2N4wI6zHE5v1Top08xiYPH28aUTNAu4vf3MBrf4MYnq7327Nj20hzIgzlRmw==} + peerDependencies: + '@expo/log-box': ^57.0.1 + expo: '*' + react: '*' + react-dom: '*' + react-native: '*' + peerDependenciesMeta: + react-dom: + optional: true + + '@expo/metro@56.0.0': + resolution: {integrity: sha512-5gIgQHtEpjjvsjKfVtIv23a98LLRV0/y07PDShEwYSytAMlE3FSF8RHXqtHc1sUJL6dn7hnuIBpIbrLXXuVi0A==} + + '@expo/osascript@2.7.1': + resolution: {integrity: sha512-Zn03EX6In7ts2lPUW2ESUSkEhEWQN1qqsiXjadtZMJOuZRkMiAg1ZQHuvz9DjByDWNJ2pBwAGyrts9lj9k389g==} + engines: {node: '>=12'} + + '@expo/package-manager@1.13.1': + resolution: {integrity: sha512-y/K+CaYYpZpNGZhSX4HyLT/vyIunFjNfyoxNysPBCefeLKI/VCx6f9LNPzrxayr3rCYO5bl9O8H+HRQK265Nkg==} + + '@expo/plist@0.8.1': + resolution: {integrity: sha512-3gTReGIUm0oRaMClsAJYxBnVPCl6fVpsl8HS+DTVxDhW4GyVyxg9E/Znm3BvcHtUJ51RJJI14pC1wvrNilCRHw==} + + '@expo/prebuild-config@57.0.8': + resolution: {integrity: sha512-NQjRuTLvxUnggK57pKTHLtzS8YYtjrQSGYu+CjgWaO8lNlqdoBbvHrxGYtc9tBuOI3xVPaTfuIvjikxX1r+UhQ==} + + '@expo/require-utils@57.0.3': + resolution: {integrity: sha512-ns05X1K8tM+Qtzp6dNloUFOopSdh3J+HC61BtOR8WHhgtPFyX8TKuO2diqZUqVg9K8yfkWug7g8tBS0qRniSTA==} + peerDependencies: + typescript: ^5.0.0 || ^5.0.0-0 || ^6.0.0 + peerDependenciesMeta: + typescript: + optional: true + + '@expo/router-server@57.0.3': + resolution: {integrity: sha512-gkboMZUv+eAK4XSBGSIQ6at3dSa/QYARm+8PKj8pMEhGnt08vgcXpWAW5rYJMoLukaDD/bUDX/JU2Iz1Azwziw==} + peerDependencies: + '@expo/metro-runtime': ^57.0.5 + expo: '*' + expo-constants: ^57.0.5 + expo-font: ^57.0.1 + expo-router: '*' + expo-server: ^57.0.1 + react: '*' + react-dom: '*' + react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 + peerDependenciesMeta: + '@expo/metro-runtime': + optional: true + expo-router: + optional: true + react-dom: + optional: true + react-server-dom-webpack: + optional: true + + '@expo/schema-utils@57.0.2': + resolution: {integrity: sha512-fMu/jyN0l1Wzv7XkeWR4IYCx1M8ryui3FdBNGrWwbRgJ7EhxXxK8E2jxP2W3pbgUwUY0V3hG8+GyfCZwny+Lxw==} + + '@expo/sdk-runtime-versions@1.0.0': + resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} + + '@expo/spawn-async@1.8.0': + resolution: {integrity: sha512-eb9xxd/LbuEGSdua4NumCu/McVB9EM+F/JxB9pWgnERw4HQ9XyTNH1KapG6oqLWR8TuRK2LQfzJlmNi94CVobw==} + engines: {node: '>=12'} + + '@expo/sudo-prompt@9.3.2': + resolution: {integrity: sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==} + + '@expo/ui@57.0.7': + resolution: {integrity: sha512-WqRVabl8VpHf3+YLHVjUy7PMIuXXI6DG88Vgmavro7Nd8Ks13h9sEJH/RLSCaJE2daVnqzEKY1v797BibuY9aw==} + peerDependencies: + '@babel/core': '*' + expo: '*' + react: '*' + react-dom: '*' + react-native: '*' + react-native-worklets: '*' + peerDependenciesMeta: + '@babel/core': + optional: true + react-dom: + optional: true + react-native-worklets: + optional: true + + '@expo/ws-tunnel@2.0.0': + resolution: {integrity: sha512-j+JfTRdCk820J9dU0sA2SqshQIKFOMo7ED84w9MJFcebfbNQgsLztEY/SABDkGnjatrW4xGqnUhVRxSBVyCkXw==} + peerDependencies: + ws: ^8.0.0 + + '@expo/xcpretty@4.4.4': + resolution: {integrity: sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==} + hasBin: true + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/react-dom@2.1.8': + resolution: {integrity: sha512-cC52bHwM/n/CxS87FH0yWdngEZrjdtLW/qVruo68qg+prK7ZQ4YGdut2GyDVpoGeAYe/h899rVeOVm6Oi40k2A==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/react@0.27.19': + resolution: {integrity: sha512-31B8h5mm8YxotlE7/AU/PhNAl8eWxAmjL/v2QOxroDNkTFLk3Uu82u63N3b6TXa4EGJeeZLVcd/9AlNlVqzeog==} + peerDependencies: + react: '>=17.0.0' + react-dom: '>=17.0.0' + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@fontsource-variable/dm-sans@5.2.8': + resolution: {integrity: sha512-AxkvMTvNWgfrmlyjiV05vlHYJa+nRQCf1EfvIrQAPBpFJW0O9VTz7oAFr9S3lvbWdmnFoBk7yFqQL86u64nl2g==} + + '@fontsource/jetbrains-mono@5.2.8': + resolution: {integrity: sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==} + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@ionic/cli-framework-output@2.2.8': + resolution: {integrity: sha512-TshtaFQsovB4NWRBydbNFawql6yul7d5bMiW1WYYf17hd99V6xdDdk3vtF51bw6sLkxON3bDQpWsnUc9/hVo3g==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-array@2.1.6': + resolution: {integrity: sha512-0JZ1Zkp3wURnv8oq6Qt7fMPo5MpjbLoUoa9Bu2Q4PJuSDWM8H8gwF3dQO7VTeUj3/0o1IB1wGkFWZZYgUXZMUg==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-fs@3.1.7': + resolution: {integrity: sha512-2EknRvMVfhnyhL1VhFkSLa5gOcycK91VnjfrTB0kbqkTFCOXyXgVLI5whzq7SLrgD9t1aqos3lMMQyVzaQ5gVA==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-object@2.1.6': + resolution: {integrity: sha512-vCl7sl6JjBHFw99CuAqHljYJpcE88YaH2ZW4ELiC/Zwxl5tiwn4kbdP/gxi2OT3MQb1vOtgAmSNRtusvgxI8ww==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-process@2.1.12': + resolution: {integrity: sha512-Jqkgyq7zBs/v/J3YvKtQQiIcxfJyplPgECMWgdO0E1fKrrH8EF0QGHNJ9mJCn6PYe2UtHNS8JJf5G21e09DfYg==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-stream@3.1.7': + resolution: {integrity: sha512-eSELBE7NWNFIHTbTC2jiMvh1ABKGIpGdUIvARsNPMNQhxJB3wpwdiVnoBoTYp+5a6UUIww4Kpg7v6S7iTctH1w==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-subprocess@3.0.1': + resolution: {integrity: sha512-cT4te3AQQPeIM9WCwIg8ohroJ8TjsYaMb2G4ZEgv9YzeDqHZ4JpeIKqG2SoaA3GmVQ3sOfhPM6Ox9sxphV/d1A==} + engines: {node: '>=16.0.0'} + + '@ionic/utils-terminal@2.3.5': + resolution: {integrity: sha512-3cKScz9Jx2/Pr9ijj1OzGlBDfcmx7OMVBt4+P1uRR0SSW4cm1/y3Mo4OY3lfkuaYifMNBW8Wz6lQHbs1bihr7A==} + engines: {node: '>=16.0.0'} '@isaacs/fs-minipass@4.0.1': resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} + '@isaacs/ttlcache@1.4.1': + resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} + engines: {node: '>=12'} + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -545,6 +1268,9 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} @@ -645,6 +1371,12 @@ packages: resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} engines: {node: '>= 10.0.0'} + '@napi-rs/wasm-runtime@1.1.6': + resolution: {integrity: sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + '@noble/hashes@1.4.0': resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} engines: {node: '>= 16'} @@ -653,8 +1385,12 @@ packages: resolution: {integrity: sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==} engines: {node: '>= 20.19.0'} + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + '@oh-my-pi/app-wire@file:vendor/app-wire/oh-my-pi-app-wire-0.6.2.tgz': - resolution: {integrity: sha512-bBQly1tpdw6NPxbtnQDdnhR6LuHySnETc9yh+/0H7y0ZNYs4M/a8pB/pjOo3Lc2nit/Jix6ocvbk8vJmqsDA9w==, tarball: file:vendor/app-wire/oh-my-pi-app-wire-0.6.2.tgz} + resolution: {integrity: sha512-6g0YDBljra0mE80tNYyxO2qouoG1+3b5SeBC3TX899+xqryvzIiOU7bpwbbrQKi3wz/50eWY0oULHiTqaFOlyA==, tarball: file:vendor/app-wire/oh-my-pi-app-wire-0.6.2.tgz} version: 0.6.2 engines: {bun: '>=1.3.14'} @@ -968,69 +1704,379 @@ packages: '@preact/signals-core@1.14.4': resolution: {integrity: sha512-HNB6HYeYKhQbJ1aKl+YRjrS4+QWHLKX6qKoUsfS/m0vqzsVaEBiZiaKbG/e+NKk2ch5ALQr/ihWaMHxiCuuWHA==} - '@rolldown/pluginutils@1.0.1': - resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} - - '@sindresorhus/is@4.6.0': - resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} - engines: {node: '>=10'} + '@radix-ui/primitive@1.1.5': + resolution: {integrity: sha512-d86WIWFYNtGA0H/d8exstrTRTp7eWJYlYJbtNofxr/3ljupZYn6EFDG/Qgu/0Kc8v7yMUxySagqJsL1+PdYjWg==} - '@standard-schema/spec@1.1.0': - resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@radix-ui/react-collection@1.1.12': + resolution: {integrity: sha512-nb67INpE0IahJKN7EYPp9m9YGwYeKlnzxT3MwXVkgCskaSJia97kG4T0ywpjNUSSnoJk/uvk12V8vbrEHEj+/Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@szmarczak/http-timer@4.0.6': - resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} - engines: {node: '>=10'} + '@radix-ui/react-compose-refs@1.1.3': + resolution: {integrity: sha512-rYOP8OMnuuPMQF1uhPVlGNcCDlkokKqGFE3JcxFViIkAXP7EvFWUliJAstrapypaBLJNHbZL6jGhbVDGTwmVhA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@tailwindcss/node@4.3.2': - resolution: {integrity: sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==} + '@radix-ui/react-context@1.2.0': + resolution: {integrity: sha512-fOE+JtN9rygNZkCnHRBEP0TAvLldlhyOxMsbwFvTP4nAs+nBmfnna+o/Zski2wkmY1YMrFC0aSzsHoLY47iLrg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@tailwindcss/oxide-android-arm64@4.3.2': - resolution: {integrity: sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] + '@radix-ui/react-dialog@1.1.19': + resolution: {integrity: sha512-+HhbN2+YtkRgVirjZ2afMeutQRuGOrdkWR5+EFC58SJojGmtyNQwYzgi6tHBpOxvFHefMtPeHdgtjz0BOGxFQg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@tailwindcss/oxide-darwin-arm64@4.3.2': - resolution: {integrity: sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] + '@radix-ui/react-direction@1.1.2': + resolution: {integrity: sha512-C3vFhbyi4SW3PmbAi6Awpu4OzJtd0MxGurvSsYtr7p7nM8RNB3VAF3CUmnp2j50knpkrRcB7+ycVXzgLgF6yNA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@tailwindcss/oxide-darwin-x64@4.3.2': - resolution: {integrity: sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] + '@radix-ui/react-dismissable-layer@1.1.15': + resolution: {integrity: sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@tailwindcss/oxide-freebsd-x64@4.3.2': - resolution: {integrity: sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] + '@radix-ui/react-focus-guards@1.1.4': + resolution: {integrity: sha512-cot/aB/mOm0IYVYTTmQcEEK1M48lZWi8FlYe5nDPQQ8NYZUlXEFgncJ9p2Kzer3RKSrY7cTTpEMLZKNo9QoP5Q==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2': - resolution: {integrity: sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] + '@radix-ui/react-focus-scope@1.1.12': + resolution: {integrity: sha512-jjk/lqTeNL0azUx5ZYzVrl4NgaDIrdzTNE4mABV9yBFI7FQqN7pIgzV1bTleUezP2QiTGA1BFTqY8MegDgWX9A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@tailwindcss/oxide-linux-arm64-gnu@4.3.2': - resolution: {integrity: sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] + '@radix-ui/react-id@1.1.2': + resolution: {integrity: sha512-orBC88futVpqCmhX1p4cvquNHsELQ+w+vBJnuj3ftETI5bJb0bZn3Tqu3SWN2IOcPycTnMGnhwoermvISt72sA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true - '@tailwindcss/oxide-linux-arm64-musl@4.3.2': - resolution: {integrity: sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] + '@radix-ui/react-portal@1.1.13': + resolution: {integrity: sha512-z3oXfmaHLJTF1wktbjgD6cn9jiEbq3WSondB10LIuIt2m2Ym4iJlrW04/euMwENDdWDdE7z+OuY7Qyp1YpRSwA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true - '@tailwindcss/oxide-linux-x64-gnu@4.3.2': - resolution: {integrity: sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==} + '@radix-ui/react-presence@1.1.7': + resolution: {integrity: sha512-zBZ4QM5XG3JRanDmqXYf3MD6th4AFXFmgU6KNMFzUaV6F3uw9I5/zjMUvFriSEn5ewo1nxuibvyxJdmLlDcslA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.7': + resolution: {integrity: sha512-bC3NiwsprbxKjuon9l7X6BUTw7FPVzEYaL92MPEY5SCd/9hUTPXVFtVwRix7778wtRsVao+zE062gL79FZleeQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.15': + resolution: {integrity: sha512-40svmmugfM3mUN7VUDGVE1tQGOhyi8enlGD0CNJEcMM36C1f71PKM21DFgNHUfem0XnA+d8H8oN3Z9ZpJjSslg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.3.0': + resolution: {integrity: sha512-MojKku4U/miO8Av4Dkb+ctMAQx7JmY96LmtDQlAarCRtd7rN52QCSzBF+XAvr5S6coSVj9HEPBgHAHKEJVk/WA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.17': + resolution: {integrity: sha512-nRyXnrAVCwjeXcHbvEbLS6ndbTeKHG1RqCP4A8Gw5L4cemDzPXdD8rAmr6wet0v57R69wGvuIIsFjHSVkZiMzQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.2': + resolution: {integrity: sha512-xCso9j1/u8sEgP1RNHjFrXJLApL8LiqOkI1R4ywuN00rxWdYg4oQXuwKLS3i0j5NWLromUD27/4nlxj2UFVvIw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.3': + resolution: {integrity: sha512-PLzC90MS+ReootmjC597dvopoelpZ8Q61HJkDXZSExitIq7PL55vHNnesAHwguHK0aPfBnpdNzQtv1uliaqQrA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.3': + resolution: {integrity: sha512-6c8ZqvPTWILEKnyVkP53EGRCcpnJiKTC21sS/6R1GF5xKyHJJWQEPfkqlcgUkdRQivd6tb23abUwe4ngWmY0JA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-is-hydrated@0.1.1': + resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.2': + resolution: {integrity: sha512-jrBWOxZITuGcnjRCM2t2U5ZPkCLxD+Ym6DjfssS5haTj2iiak/DOb64JeN6OdLfLgptb6/e2kKR+ZuTrGoZTPA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@react-native-masked-view/masked-view@0.3.2': + resolution: {integrity: sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ==} + peerDependencies: + react: '>=16' + react-native: '>=0.57' + + '@react-native/assets-registry@0.86.0': + resolution: {integrity: sha512-nIaXbm2jX1OTYp0qbviJ3O6KZivoE8z3BnhUQ2LsqfZSWRoOK/n1qsiAr6oALiNKWnXY3j2KPwtYORnZzp8xew==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/babel-plugin-codegen@0.86.0': + resolution: {integrity: sha512-qdsABWNW7uTll90l4Vh03gjeyu3WVDi2CyiiyvYGMRDcoYbjbQi6df3BMAm9lQI2yslZ1T14LlDDAsgTwNxplA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/babel-preset@0.86.0': + resolution: {integrity: sha512-bYQcWiPySNvF4dns9Ls9gMmwgq66ohvM9Fwc/Kn8r85t66UNHxch3p1QwPiSorDelFauZwJbgo9+ReibTgvpbA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + + '@react-native/codegen@0.86.0': + resolution: {integrity: sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + + '@react-native/community-cli-plugin@0.86.0': + resolution: {integrity: sha512-Jv8p1ebEPfTzs8gmrjsdT2XMXFfeAg45Pman+XPLFGaSeGAZkutRFRyX9Cs9aGTSOyIA9YPJ6vDNb1ayTf1FKQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@react-native-community/cli': '*' + '@react-native/metro-config': 0.86.0 + peerDependenciesMeta: + '@react-native-community/cli': + optional: true + '@react-native/metro-config': + optional: true + + '@react-native/debugger-frontend@0.86.0': + resolution: {integrity: sha512-7Mb3nDfyJeys+ELF75Ageu7VKERlnIMoO+aNPoXqTXvz+b41L6l2CqMyLpDHxkBSlenij6gEepPNgaIyWHbJZw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/debugger-shell@0.86.0': + resolution: {integrity: sha512-Y0zEkZzLz8ou6o/VLml1A31X/rMgc6DRjwxwzPMa94qRTMY070WeBCNTITQo4kKTBAUgbxh07oXPQqp0Tpja8w==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/dev-middleware@0.86.0': + resolution: {integrity: sha512-20pTO6yTybmvXvro520H6C7jydIQnLKOl5qFtVEcHSdFrY63r3OGei+Rx9bILgSRmH6jgnfEcijcMx7pwWuQtw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/gradle-plugin@0.86.0': + resolution: {integrity: sha512-a1RcfaEDqWExCGfCwadIxt4l8FvKYgFqeMf2uzeKyAOnb+vTGNIeCvifFL2MqvgaeYxlER437HbMIajGcuJ1pQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/js-polyfills@0.86.0': + resolution: {integrity: sha512-zYy/Cjd1VTnZ2iCNaG9bDF9C3l2ntESiPRscjIlI5FKugu6aeTwsDSv1aI8Bc4Kp3vEdoVg+UQhLAhE4svREaQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/metro-babel-transformer@0.86.0': + resolution: {integrity: sha512-SjKej3E5qIahqo/G+rSOrmJUQM44RyKtWtO+VfmKAAMoJWkBFomM22hTLKCIS5cdbIAJ9COAmU+KAi2wVSO0wQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@babel/core': '*' + + '@react-native/metro-config@0.86.0': + resolution: {integrity: sha512-7v+xbTeEci9ZcQ/Z1OqI4RXcqN69wSMDYL5BAMvOReZ7U04+aDQ0/SQhClYPn6x2/RxM4WzMKSAuNyLKqvYVtw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + '@react-native/normalize-colors@0.74.89': + resolution: {integrity: sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==} + + '@react-native/normalize-colors@0.86.0': + resolution: {integrity: sha512-kG0wfCGghUKlfxkJyyHCDVutWVYWK7/DG58ojA/4v9EfulgF+osuSQmlbNb3rcKX58qutm7JcldSeVLgGFha9g==} + + '@react-native/virtualized-lists@0.86.0': + resolution: {integrity: sha512-4/ZLXdf/OSpPDVO0AsQ1SJdRIzt5t9BNQ46QwGgxvX7/cirYR5k8KXctNGGgW8lQo2gZChEfY2zFCZg9nM/jiw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + peerDependencies: + '@types/react': ^19.2.0 + react: '*' + react-native: 0.86.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@sinclair/typebox@0.27.12': + resolution: {integrity: sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==} + + '@sindresorhus/is@4.6.0': + resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} + engines: {node: '>=10'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@szmarczak/http-timer@4.0.6': + resolution: {integrity: sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==} + engines: {node: '>=10'} + + '@tailwindcss/node@4.3.2': + resolution: {integrity: sha512-yWP/sqEcBLaD8JuA6zNwxoYKr75qxTioYwlRwekj5Jr/I5GXnoJfjetH/psLUIv74cYTH2lBUEzBkinthoYcBg==} + + '@tailwindcss/oxide-android-arm64@4.3.2': + resolution: {integrity: sha512-WHxqIuHpvZ5VtdX6GTl1Ik/Vp2YuN42Et+0CdeaVd/frQ9jAvGmvR8vLT+jk3e8/Q3x8kECB9+R17pgpp2BulA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.3.2': + resolution: {integrity: sha512-GZypeUY/IDJW3877KeM+O67vbXr3MBnbtEL4aYhNErv/JWZhye2vGSWWG9tB6iiqR2MqRNkY8IOUy4NdSZV26w==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.3.2': + resolution: {integrity: sha512-UIIzmefR6KO1sDU7MzRqAxC8iBpft/VhkGjTjnhoS6k7Z3rQ9wEgA1ODSiyH/tcSYssulNm4Ci3hOeK1jH7ccQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.3.2': + resolution: {integrity: sha512-GN+uAmcI6DNspnCDwtOAZrTz6oukJnp337qZvxqCGLd3BHBzJpO0ZbTLRvJNdztOeAmTzewewGIMPb0tk2R4WA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.3.2': + resolution: {integrity: sha512-4ABn7qSbdHRwTiDiuWNegCyb5+2FJ4vKIKc3DmKrvAFw7MU1Lm11dIkTPwUaFdTzc7IsOpDbqBrlh0x6y36U/w==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.3.2': + resolution: {integrity: sha512-wDgEIGwoM8w8pufh9LVt1PahDgNdKXrLC2qfAnV3vAmococ9RWbxeAw4pxPttd/TsJfwjyLf90Dg1y9y8I6Emw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.3.2': + resolution: {integrity: sha512-J5Nuk0uZQIiMTJj3LEx4sAA9tMFUoXQZFv1J6An+QGYe53HKRJuFDi0rpq/tuouCZeAbOBY3kQ6g8qeD4TUjtA==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.3.2': + resolution: {integrity: sha512-kqCZpSKOBEJO4mz7OqWoofBZeXTAwaVGPj0ErAj7CojmhKpWVWVOnrt9dE8odoIraZq4oj3ausM37kXi+Tow8w==} engines: {node: '>= 20'} cpu: [x64] os: [linux] @@ -1104,12 +2150,19 @@ packages: resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} engines: {node: '>=18'} + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + '@testing-library/user-event@14.6.1': resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} engines: {node: '>=12', npm: '>=6'} peerDependencies: '@testing-library/dom': '>=7.21.4' + '@tybys/wasm-util@0.10.3': + resolution: {integrity: sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -1137,12 +2190,30 @@ packages: '@types/fs-extra@9.0.13': resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} + '@types/hammerjs@2.0.46': + resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} + '@types/hast@3.0.5': resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -1163,6 +2234,9 @@ packages: peerDependencies: '@types/react': ^19.2.0 + '@types/react-test-renderer@19.1.0': + resolution: {integrity: sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==} + '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} @@ -1181,9 +2255,74 @@ packages: '@types/ws@8.5.13': resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + '@typescript-eslint/eslint-plugin@8.64.0': + resolution: {integrity: sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.64.0 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.64.0': + resolution: {integrity: sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.64.0': + resolution: {integrity: sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.64.0': + resolution: {integrity: sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.64.0': + resolution: {integrity: sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.64.0': + resolution: {integrity: sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.64.0': + resolution: {integrity: sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.64.0': + resolution: {integrity: sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.64.0': + resolution: {integrity: sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.64.0': + resolution: {integrity: sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260604.1': resolution: {integrity: sha512-zs616um9UuaODLsNlCu5Aw95rFcTV4u3hVt090r6k0lVvTxfaJOv8HKA6BpIotcEYlZlMQowrMSYCCdedo7iyA==} engines: {node: '>=16.20.0'} @@ -1234,6 +2373,126 @@ packages: '@ungap/structured-clone@1.3.2': resolution: {integrity: sha512-5jsZFwgR5rTdKwidH9Qmat75RKwqfpKlWWB1frDkljN127mwqBu8K0PYo7/hFpF03IEJpfVPpCQDY/eDx3iHvA==} + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + resolution: {integrity: sha512-g5T90pqg1bo/7mytQx6F4iBNC0Wsh9cu+z9veDbFjc7HjpesJFWD7QMS0NGStXM075+7dJPPVvBbpZlnrdpi/w==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.12.2': + resolution: {integrity: sha512-YGCRZv/9GLhwmz6mYDeTsm/92BAyR28l6c2ReweVW5pWgfsitWLY8upvfRlGdoyD8HjeTHSYJWyZGD4KJA/nFQ==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + resolution: {integrity: sha512-u9DiNT1auQMO20A9SyTuG3wUgQWB9Z7KjAg0uFuCDR1FsAY8A0CG2S6JpHS1xwm/w1G08bjXZDcyOCjv1WAm2w==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.12.2': + resolution: {integrity: sha512-f7rPLi/T1HVKZu/u6t87lroib16n8vrSzcyxI7lg4BGO9UF26KhQL44sd9eOUgrTYhvRXtWOIZT5PejdPyJfUA==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + resolution: {integrity: sha512-BpcOjWCJub6nRZUS2zA20pmLvjtqAtGejETaIyRLiZiQf++cbrjltLA5NN/xaXfqeOBOSlMFbemIl5/S5tljmg==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + resolution: {integrity: sha512-vZTDvdSISZjJx66OzJqtsOhzifbqRjbmI1Mnu49fQDwog5GtDI4QidRiEAYbZCRj9C8YZEW+3ZjqsyS9GR4k2A==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + resolution: {integrity: sha512-BiPI+IrIlwcW4nLLMM21+B1dFPzd55yAVgVGrdgDjNef+ch03GdxrcyaIz8X9SsQirh/kCQ7mviyWlMxdh2D7g==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + resolution: {integrity: sha512-zJc0H99FEPoFfSrNpa91HYfxzfAJCr502oxNK1cfdC9hlaFI43RT+JFCann9JUgZmLzzntChHyn13Sgn9ljHNg==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + resolution: {integrity: sha512-KQ3Lki6l+Pz1k/eBipN41ES+YUK30beLGb9YqcB1O542cyLCNE6GaxrfcY3T6EezmGGk84wb5XyO9loTM9tkcA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + resolution: {integrity: sha512-3SJGEh1DborhG6pyxvhPzCT4bbSIVihsvgJc13P1bHG7KLdNDaF9T3gsTwFc7Jw/5Y5/iWOjkEx7Zy0NvCGX3Q==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + resolution: {integrity: sha512-jiuG/Obbel7uw1PwHNFfrkiKhLAF6mnyZ6aWlOAVN9WqKm8v0OFGnciJIHu8+CMvXLQ8AD51LPzAoUfT21D5Ew==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + resolution: {integrity: sha512-q7xRvVpmcfeL+LlZg8Pbbo6QaTZwDU5BaGZbwfhkEsXJn3Was8xYfE0RBH266xZt0rM6B7i8xAYIvjthuUIWHg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + resolution: {integrity: sha512-0CVdx6lcnT3Q9inOH8tsMIOJ6ImndllMjqJHg8RLVdB7Vq4SfkEXl9mCSsVNuNA4MCYycRicCUxPCabVHJRr6A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + resolution: {integrity: sha512-iOwlRo9vnp6R6ohHQS11n0NnfdXx/omhkocmIfaPRpQhKZ+3BDMkkdRVh53qjkFkpPddf+FETA28NwGN7l5l+w==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + resolution: {integrity: sha512-HYJtLfXq94q8iZNFT1lknx258wlkkWhZeUXJRqzKBBUJ00CvZ+N33zgbCqimLjsyw5Va6uUxhVa12mI+kaveEw==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + resolution: {integrity: sha512-mPsUhunKKDih5O96Y6enDQyHc1SqBPlY1E/SfMWDM3EdJ95Z9CArPeCVwCCqbP45ljvivdEk8Fxn+SIb1rDAJQ==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + resolution: {integrity: sha512-azrt6+5ydLd8Vt210AAFis/lZevSfPw93EJRIJG+xPu4WCJ8K0kppCTpMyLPcKT7H15M4Jnt2tMp5bOvCkRC6A==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + resolution: {integrity: sha512-YZ9hP4O0X9PQb8eO980qmLNGH4zT3I9+SZTdt0Pr0YyuGQhYKoOZkV02VzrzyOZJ5xIJ3UFIenKkUkGg8GjgWQ==} + cpu: [arm64] + os: [openharmony] + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + resolution: {integrity: sha512-tYFDIkMxSflfEc/h92ZWNsZlHSwgimbNHSO3PL2JWQHfCuC2q316jMyYU9TIWZsFK2bQwyK5VAdYgn8ygPj69A==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + resolution: {integrity: sha512-qzNyg3xL0VPQmCaUh+N5jSitce6k+uCBfMDesWRnlULOZaqUkaJ0ybdT+UqlAWJoQjuqfIU/0Ptx9bteN4D82g==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + resolution: {integrity: sha512-WD9sY00OfpHVGfsnHZoA8jVT+esS/Bg8z8jzxp5BnDCjjwsuKsPQrzswwpFy4J1AUJbXPRfkpcX0mXrzeXW79g==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + resolution: {integrity: sha512-nAB74NfSNKknqQ1RrYj6uz8FcXEomu/MATJZxh/x+BArzN2U3JbOYC0APYzUIGhVY3m5hRxA8VPNdPBoG8txlA==} + cpu: [x64] + os: [win32] + '@vitejs/plugin-react@6.0.3': resolution: {integrity: sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1409,10 +2668,37 @@ packages: resolution: {integrity: sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA==} engines: {node: ^20.17.0 || >=22.9.0} - agent-base@7.1.4: + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.17.0: + resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} engines: {node: '>= 14'} + agent-cli-detector@0.1.3: + resolution: {integrity: sha512-XjBe6lT5sK9xhAn0ToFvdwicsaGvzZkax1iviPnL8sFFYHoagnTTn2E4YTFiyM956iqkOkFcgcWCiER0kkShbQ==} + engines: {node: '>=18.18'} + hasBin: true + ajv-formats@2.1.1: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -1421,13 +2707,31 @@ packages: ajv: optional: true + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + anser@1.4.10: + resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1443,12 +2747,54 @@ packages: dmg-builder: 26.15.6 electron-builder-squirrel-windows: 26.15.6 + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + asn1js@3.0.10: resolution: {integrity: sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==} engines: {node: '>=12.0.0'} @@ -1465,6 +2811,10 @@ packages: resolution: {integrity: sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==} engines: {node: '>=0.12.0'} + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + async@3.2.6: resolution: {integrity: sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==} @@ -1479,9 +2829,61 @@ packages: resolution: {integrity: sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==} engines: {node: '>=10.12.0'} + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + aws4@1.13.2: resolution: {integrity: sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==} + babel-plugin-polyfill-corejs2@0.4.17: + resolution: {integrity: sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.8: + resolution: {integrity: sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-react-compiler@1.0.0: + resolution: {integrity: sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==} + + babel-plugin-react-native-web@0.21.2: + resolution: {integrity: sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==} + + babel-plugin-syntax-hermes-parser@0.36.0: + resolution: {integrity: sha512-LhD0xdoedDw7ansQgXbB2DADLZIK/LRXuWNBPuVzMc5S2WK5GyT89tCM+cQzxFGO0mGyLK6D5TrVOJJzAoDy8Q==} + + babel-plugin-syntax-hermes-parser@0.36.1: + resolution: {integrity: sha512-ycduwJbvdvIMmVvlAZqGggS+pm5Eu4Bk9pcV9Sm2Z4PJNRVsKkv0g7vHj+LeuC1gHTeF67sJXFOq61IlqCa2hA==} + + babel-plugin-transform-flow-enums@0.0.2: + resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} + + babel-preset-expo@57.0.3: + resolution: {integrity: sha512-JuTLwC4dt30GF3L8sY7EBwh5iD7L3dSWumfg+i99bFK2SIXWyfn01UHxu+azfKXFU/ufim9oUt9KUoJ9AvyOPA==} + peerDependencies: + '@babel/runtime': ^7.20.0 + expo: '*' + expo-widgets: ^57.0.5 + react-refresh: '>=0.14.0 <1.0.0' + peerDependenciesMeta: + '@babel/runtime': + optional: true + expo: + optional: true + expo-widgets: + optional: true + + badgin@1.2.3: + resolution: {integrity: sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -1495,6 +2897,11 @@ packages: base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + baseline-browser-mapping@2.10.43: + resolution: {integrity: sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==} + engines: {node: '>=6.0.0'} + hasBin: true + big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -1506,6 +2913,13 @@ packages: resolution: {integrity: sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==} deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. + bplist-creator@0.1.0: + resolution: {integrity: sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==} + + bplist-parser@0.3.1: + resolution: {integrity: sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==} + engines: {node: '>= 5.10.0'} + bplist-parser@0.3.2: resolution: {integrity: sha512-apC2+fspHGI3mMKj+dGevkGo/tCqVB8jMb6i+OX+E29p0Iposz07fABkRIfVUPNd5A5VbuOz1bZbnmkKLYF+wQ==} engines: {node: '>= 5.10.0'} @@ -1520,6 +2934,18 @@ packages: resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==} engines: {node: 18 || 20 || >=22} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.6: + resolution: {integrity: sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-crc32@0.2.13: resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} @@ -1534,6 +2960,10 @@ packages: resolution: {integrity: sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==} engines: {node: '>=14.0.0'} + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + bytestreamjs@2.0.1: resolution: {integrity: sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==} engines: {node: '>=6.0.0'} @@ -1550,6 +2980,25 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001806: + resolution: {integrity: sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==} + ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -1557,6 +3006,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -1577,9 +3030,24 @@ packages: resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} engines: {node: '>=18'} + chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-edge-launcher@0.3.0: + resolution: {integrity: sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==} + chromium-pickle-js@0.2.0: resolution: {integrity: sha512-1R5Fho+jBq0DDydt+/vHWj5KJNJCKdARKOCwZUen84I5BreWoLqRLANH1U87eJy1tiASPtMnGqJJq0ZsLoRPOw==} + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + ci-info@4.3.1: resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} engines: {node: '>=8'} @@ -1591,6 +3059,17 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + cli-cursor@2.1.0: + resolution: {integrity: sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==} + engines: {node: '>=4'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1598,17 +3077,34 @@ packages: clone-response@1.0.3: resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1620,10 +3116,17 @@ packages: resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} engines: {node: '>=18'} + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + commander@5.1.0: resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} engines: {node: '>= 6'} + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + commander@9.5.0: resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} engines: {node: ^12.20.0 || >=14} @@ -1632,6 +3135,14 @@ packages: resolution: {integrity: sha512-pJDh5/4wrEnXX/VWRZvruAGHkzKdr46z11OlTPN+VrATlWWhSKewNCJ1futCO5C7eJB3nPMFZA1LeYtcFboZ2A==} engines: {node: '>=0.10.0'} + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -1639,29 +3150,73 @@ packages: resolution: {integrity: sha512-8fLl9F04EJqjSqH+QjITQfJF8BrOVaYr1jewVgSRAEWePfxT0sku4w2hrGQ60BC/TNLGQ2pgxNlTbWQmMPFvXg==} engines: {node: '>=12'} + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} cookie-es@3.1.1: resolution: {integrity: sha512-UaXxwISYJPTr9hwQxMFYZ7kNhSXboMXP+Z3TRX6f1/NyaGPfuNUZOWP1pUEb75B2HjfklIYLVRfWiFZJyC6Npg==} + core-js-compat@3.49.0: + resolution: {integrity: sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} cross-dirname@0.1.0: resolution: {integrity: sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==} + cross-fetch@3.2.0: + resolution: {integrity: sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-in-js-utils@3.1.0: + resolution: {integrity: sha512-fJAcud6B3rRu+KHYk+Bwf+WFL2MDCJJ1XG9x137tJQ0xYxor7XziQtuGFbWNdqrvF4Tk26O3H73nfVqXt/fW1A==} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + debounce-fn@4.0.0: resolution: {integrity: sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==} engines: {node: '>=10'} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1674,10 +3229,24 @@ packages: decode-named-character-reference@1.3.0: resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -1698,14 +3267,25 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -1718,9 +3298,19 @@ packages: dmg-builder@26.15.6: resolution: {integrity: sha512-nr5vQxEhM0REomp1qiHbc6V99yrfBZy+wUU56VXADfSOlLj8PdLqsHiRe7b+FbqKesiyv4ax+k1GVwGonYKuCg==} + dnssd-advertise@1.1.6: + resolution: {integrity: sha512-Ndrrf6BMPalkQPd/zubL+4YghH2J9NspapQ09uDXwYbvOPkP0oaqf5CkcwJ0b50kS2O3ul6yVu+jz+RY62Cejg==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dot-prop@6.0.1: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} @@ -1740,6 +3330,9 @@ packages: duplexer2@0.1.4: resolution: {integrity: sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + ejs@3.1.10: resolution: {integrity: sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==} engines: {node: '>=0.10.0'} @@ -1759,6 +3352,9 @@ packages: electron-store@8.2.0: resolution: {integrity: sha512-ukLL5Bevdil6oieAOXz3CMy+OgaItMiVBg701MNlG6W5RaC0AHN7rvlqTCmeb6O7jP0Qa1KKYTE0xV0xbhF4Hw==} + electron-to-chromium@1.5.393: + resolution: {integrity: sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==} + electron-updater@6.8.9: resolution: {integrity: sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig==} @@ -1778,6 +3374,14 @@ packages: emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -1796,6 +3400,17 @@ packages: err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + + es-abstract-get@1.0.0: + resolution: {integrity: sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==} + engines: {node: '>= 0.4'} + + es-abstract@1.24.2: + resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} + engines: {node: '>= 0.4'} + es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1804,6 +3419,10 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} + es-iterator-helpers@1.4.0: + resolution: {integrity: sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==} + engines: {node: '>= 0.4'} + es-module-lexer@2.3.0: resolution: {integrity: sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==} @@ -1815,6 +3434,14 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.4: + resolution: {integrity: sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==} + engines: {node: '>= 0.4'} + es6-error@4.1.1: resolution: {integrity: sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==} @@ -1822,6 +3449,13 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1830,82 +3464,452 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - estree-util-is-identifier-name@3.0.0: - resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + eslint-config-expo@57.0.0: + resolution: {integrity: sha512-T7OTN9xrSZYjLw4qTkL1Mn2WfAUVmMGY38+OYAcraI1uiTFVH6jfkSkv84WLTqnneblLcb6AsMDV+SHcUj3hGw==} + peerDependencies: + eslint: '>=8.10' - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + eslint-import-resolver-node@0.3.10: + resolution: {integrity: sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==} - expect-type@1.4.0: - resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} - engines: {node: '>=12.0.0'} + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true - exponential-backoff@3.1.3: - resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + eslint-module-utils@2.14.0: + resolution: {integrity: sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + eslint-plugin-expo@1.1.0: + resolution: {integrity: sha512-vPP0EPx7IA7ZfP49dY4rq9RV5jqkFWG+Pih3/oGjzIRjMI+ogcOE8i6isYkLXAdw/yvFV2BRZkTaQaiOGQqn6Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + eslint: '>=8.10' - extract-zip@2.0.1: - resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} - engines: {node: '>= 10.17.0'} - hasBin: true + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + eslint-plugin-react-hooks@7.1.1: + resolution: {integrity: sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0 - fast-uri@3.1.3: - resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - fd-slicer@1.1.0: - resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.5: + resolution: {integrity: sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true peerDependencies: - picomatch: ^3 || ^4 + jiti: '*' peerDependenciesMeta: - picomatch: + jiti: optional: true - filelist@1.0.6: - resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} - - find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - form-data@4.0.6: - resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} - engines: {node: '>= 6'} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} - fs-extra@11.3.1: - resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} - engines: {node: '>=14.14'} + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} - fs-extra@11.3.6: - resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} - engines: {node: '>=14.14'} + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} - fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - fs-extra@8.1.0: - resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} - engines: {node: '>=6 <7 || >=8'} + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} - fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + expo-application@57.0.2: + resolution: {integrity: sha512-q31YwcXyymviAmdrtDfAg3Dld4VMxLCNAfgMHip7vZpPX4lzF/AfwsywqBJUAjQnJknzaNAkzqvMVjO5XmKYDA==} + peerDependencies: + expo: '*' + + expo-asset@57.0.6: + resolution: {integrity: sha512-n3Yb1VxcP+BMRTyC4R1x2It4+m5EDkNXiVCHGWbnIREQUUkMs2Yeul7D5qfFWAYtIn2Z3hbGMndwU6Az1FPSEg==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-constants@57.0.6: + resolution: {integrity: sha512-OV+4XUshdO18TKNlo1cxUkXeJWgUOPgalvl8ofmc7kmPPHoyfz2hGJ94tyY/RND/GG5RREE+me9YHClNEzo+Ow==} + peerDependencies: + expo: '*' + react-native: '*' + + expo-file-system@57.0.1: + resolution: {integrity: sha512-w7/ERvQFrGP2apTO9lDtZ+O6JQIhfakL7+Xqzh+rfMO9B4LB4qwrz+YvLgir8KFRVX64JHBnRuYBVLY1oQZcqw==} + peerDependencies: + expo: '*' + react-native: '*' + + expo-font@57.0.1: + resolution: {integrity: sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-glass-effect@57.0.1: + resolution: {integrity: sha512-m/n8maxqNcHk6ZDhuqXBfD5Kt1Iz3M8xykVgdB0iSCIXvF70IqWXmQhX8Psswhrp8eZ+3r0mAD0Jh/2gFA3QaA==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-keep-awake@57.0.1: + resolution: {integrity: sha512-28lkFImeXTS+bhAjuCFV7w7tW5bXg27BJVrxv+nC/nyYa86qEa0oFeHwqol6ha5k4pdVDQgBF09GM4A1k76Ssg==} + peerDependencies: + expo: '*' + react: '*' + + expo-linking@57.0.3: + resolution: {integrity: sha512-uF5PFfIQ0cjEGijzFuN8gU5eLqWs6BgS29Zp4ddP79XPMobZmS6qzdGyCOZTnZz3UWR17rFAJst1UntuZg9/6Q==} + peerDependencies: + react: '*' + react-native: '*' + + expo-modules-autolinking@57.0.8: + resolution: {integrity: sha512-YBDgbJHlhhhr3JaKErW6znsIL8zQsh206LaDpWrLHV/WluRwhYfZuF/fhkVN7b/DsRxIu0UeCPExaEPMxlC2Hw==} + hasBin: true + + expo-modules-core@57.0.6: + resolution: {integrity: sha512-hePwOh2+i+EpWrVnv95sQeQ0OD5PYuEuIhmglVLzQVaVBV3zHcRvLAc1rV8ROqUL6YtAQUXjPkoSx1ly2ZdTuQ==} + peerDependencies: + react: '*' + react-native: '*' + react-native-worklets: ^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0 + peerDependenciesMeta: + react-native-worklets: + optional: true + + expo-modules-jsi@57.0.3: + resolution: {integrity: sha512-B+iXJCC5OIXjFKkLLSY2DZ8BGS+hC3PBTrALpV43pHqhXqqZrRH3uTEylZgsoKQO8N8tjmjAua2ucTFHtr1o0w==} + peerDependencies: + react-native: '*' + + expo-notifications@57.0.6: + resolution: {integrity: sha512-eRzJrcJiaIPTGnI916WZ0NL6vzNDE6WeOdvyDzwSPQrYsoinPZSp0f8hudLrE54crysyAO/rdrOrmJBXsx9w4g==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-router@57.0.7: + resolution: {integrity: sha512-2CLsLlcvvf3zGgHPJRTOrCPVK1l97gLGDTEbGAFcZ379ipGvfWfz9tiOWqCN3uGcl70blVUjEw8WyuvIfsfH6A==} + peerDependencies: + '@expo/log-box': ^57.0.1 + '@expo/metro-runtime': ^57.0.6 + '@testing-library/react-native': '>= 13.2.0' + expo: '*' + expo-constants: ^57.0.6 + expo-linking: ^57.0.3 + react: '*' + react-dom: '*' + react-native: '*' + react-native-gesture-handler: '*' + react-native-reanimated: '*' + react-native-safe-area-context: '>= 5.4.0' + react-native-screens: ^4.25.2 + react-native-web: '*' + react-server-dom-webpack: ~19.0.4 || ~19.1.5 || ~19.2.4 + peerDependenciesMeta: + '@testing-library/react-native': + optional: true + react-dom: + optional: true + react-native-gesture-handler: + optional: true + react-native-reanimated: + optional: true + react-native-web: + optional: true + react-server-dom-webpack: + optional: true + + expo-secure-store@57.0.1: + resolution: {integrity: sha512-tLa1VmSadOq19mA/dwkl99RbHyjLE0T1qqBYMY3/OsguZTI+rlrDy/DDJjupqlVtmr95hD7o1pYqx5aL+B4YMA==} + peerDependencies: + expo: '*' + + expo-server@57.0.1: + resolution: {integrity: sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w==} + engines: {node: '>=20.16.0'} + + expo-speech-recognition@56.0.1: + resolution: {integrity: sha512-TpP1KCiq3vYfSQF0XpUkWLXp4mTw6MLuyMGPzVVKwqzs7oiKlJ/e0q3PNNjfqs058X47ftqT+31lTaWRWRbmPg==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-speech@57.0.1: + resolution: {integrity: sha512-xRI5FyAju4rK10xcVE+3SjdrF1ni24+Sj8ujSrylBsbrwFrrLO19H5DkG6EzXXVQ6lWdn+dC0JCr8OLaM7HI+Q==} + peerDependencies: + expo: '*' + + expo-splash-screen@57.0.4: + resolution: {integrity: sha512-AspaOA5VZUl8yXb/BQORrRdOs0f7F/IgmvKNdCFaUDuz4X+j/4bPkkyssARFKwC5A5IQ3g7QS/Ve1XxDiDZ38Q==} + peerDependencies: + expo: '*' + + expo-status-bar@57.0.1: + resolution: {integrity: sha512-Xwaq1gAoVRWx5dPG5VhT5RSbnI9OilhZnO5qoPBnUaBAa5VzRzfdS8q0/bsPt0jR2DKLtGuP0bQ6efMJ4RIMDg==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-symbols@57.0.1: + resolution: {integrity: sha512-8Zf+a83OywV0vf1NUtSKpNqKcULmO0GTI+zfFnGYl7SLDH9FjL5RcEZoy6CHvCgq2KDrQF21pl3r7Tb4ItPscw==} + peerDependencies: + expo: '*' + expo-font: '*' + react: '*' + react-native: '*' + + expo-system-ui@57.0.1: + resolution: {integrity: sha512-r8a6Jk2suL0vI7Uq4iKJab5Eesk8dkB56Q6HksVNkzuAExV0axoikQwZv8aAyHGbu2VHp0artB0N1/PQDLSgBg==} + peerDependencies: + expo: '*' + react-native: '*' + react-native-web: '*' + peerDependenciesMeta: + react-native-web: + optional: true + + expo@57.0.7: + resolution: {integrity: sha512-PJdE0EjoX878OqClmsigVKdT0jCNOAQDRLcvFkeBakxaVyEDhjcQ8baKq2YysYH2g0YNB1rEIpHUiKtluO918A==} + hasBin: true + peerDependencies: + '@expo/dom-webview': '*' + '@expo/metro-runtime': '*' + react: '*' + react-dom: '*' + react-native: '*' + react-native-web: '*' + react-native-webview: '*' + peerDependenciesMeta: + '@expo/dom-webview': + optional: true + '@expo/metro-runtime': + optional: true + react-dom: + optional: true + react-native-web: + optional: true + react-native-webview: + optional: true + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-uri@3.1.3: + resolution: {integrity: sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==} + + fb-dotslash@0.5.8: + resolution: {integrity: sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==} + engines: {node: '>=20'} + hasBin: true + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fbjs-css-vars@1.0.2: + resolution: {integrity: sha512-b2XGFAFdWZWg0phtAWLHCk836A1Xann+I+Dgd3Gk64MHKZO44FfoD1KxyvbSh0qZsIoXQGGlVztIY+oitJPpRQ==} + + fbjs@3.0.5: + resolution: {integrity: sha512-ztsSx77JBtkuMrEypfhgc3cI0+0h+svqeie7xHbh1k/IKdcydnvadp/mUaGgjAOXQmQSxsqgaRhS3q9fy+1kxg==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fetch-nodeshim@0.4.10: + resolution: {integrity: sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + filelist@1.0.6: + resolution: {integrity: sha512-5giy2PkLYY1cP39p17Ech+2xlpTRL9HLspOfEgm0L6CwBXBTgsK5ou0JtzYuepxkaQ/tvhCFIJ5uXo0OrM2DxA==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + flow-enums-runtime@0.0.6: + resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + + fontfaceobserver@2.3.0: + resolution: {integrity: sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + form-data@4.0.6: + resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==} + engines: {node: '>= 6'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@11.3.1: + resolution: {integrity: sha512-eXvGGwZ5CL17ZSwHWd3bbgk7UUpF6IFHtP57NYYakPvHOs8GDgDe5KJI36jIJzDkJ6eJjuzRA8eBQb6SkKue0g==} + engines: {node: '>=14.14'} + + fs-extra@11.3.6: + resolution: {integrity: sha512-w8ZNZr2mKIc7qeNaQ9AVPT1+iFaI+Avd4xudVOvdDJ8VytREi1Ft5Ih7hd9jjehod8vAM5GMsfQ/TpPf4EyoEA==} + engines: {node: '>=14.14'} + + fs-extra@7.0.1: + resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} fsevents@2.3.2: resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} @@ -1920,6 +3924,21 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + function.prototype.name@1.2.0: + resolution: {integrity: sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} @@ -1928,6 +3947,10 @@ packages: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} @@ -1936,6 +3959,21 @@ packages: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + getenv@2.0.0: + resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} + engines: {node: '>=6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} @@ -1948,6 +3986,14 @@ packages: resolution: {integrity: sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==} engines: {node: '>=10.0'} + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.5.0: + resolution: {integrity: sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==} + engines: {node: '>=18'} + globalthis@1.0.4: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} @@ -1963,6 +4009,14 @@ packages: graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1970,6 +4024,10 @@ packages: has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -2006,10 +4064,44 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hermes-compiler@250829098.0.14: + resolution: {integrity: sha512-5meXwsZxgiqFaJjNzwjzI9IyUkuGGBisu+z9BvQWmGVpjH6nz11hgqkyxe4dl8UAdyIV4lTbz91+Dlnjz0VxqA==} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-estree@0.35.0: + resolution: {integrity: sha512-xVx5Opwy8Oo1I5yGpVRhCvWL/iV3M+ylksSKVNlxxD90cpDpR/AR1jLYqK8HWihm065a6UI3HeyAmYzwS8NOOg==} + + hermes-estree@0.36.0: + resolution: {integrity: sha512-A1+8zn5oss2CFP7pKsOaxorQG6FNIz1WU1VDqruLPPZl3LVgeE2C5xfFg8Ow6/Ow4mSslLLtYP1J3n38eKyW9w==} + + hermes-estree@0.36.1: + resolution: {integrity: sha512-guv1nQ6IJ7S83NRFPWc3SA7IBZrdNC9kapwOq6uXvF4wP+sDCgjzQbKPCoyYmoyZRzztF/n/c36l/rccCZSiCw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + hermes-parser@0.35.0: + resolution: {integrity: sha512-9JLjeHxBx8T4CAsydZR49PNZUaix+WpQJwu9p2010lu+7Kwl6D/7wYFFJxoz+aXkaaClp9Zfg6W6/zVlSJORaA==} + + hermes-parser@0.36.0: + resolution: {integrity: sha512-GdpwMmH5x6IpC1cijvcvYnlPB60Mh6kTSF/NFdYV/j56gYdi+0RIakYs+eqOV+bbO0SW7mgVVGSsTJxyPQfo3w==} + + hermes-parser@0.36.1: + resolution: {integrity: sha512-GApNk4zLHi2UWoWZZkx7LNCOSzLSc5lB55pZ/PhK7ycFeg7u5LcF88p/WbpIi1XUDtE0MpHE3uRR3u3KB7TjSQ==} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + hosted-git-info@4.1.0: resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} engines: {node: '>=10'} + hosted-git-info@7.0.2: + resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==} + engines: {node: ^16.14.0 || >=18.0.0} + html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} @@ -2019,6 +4111,10 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} @@ -2031,6 +4127,34 @@ packages: resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} + hyphenate-style-name@1.1.0: + resolution: {integrity: sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + engines: {node: '>= 4'} + + image-size@1.2.1: + resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} + engines: {node: '>=16.x'} + hasBin: true + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -2045,27 +4169,111 @@ packages: inline-style-parser@0.2.7: resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + inline-style-prefixer@7.0.1: + resolution: {integrity: sha512-lhYo5qNTQp3EvSSp3sRvXMbVQTLrvGV6DycRMJ5dm2BLMiJ30wpXKdDdgX+GmJZ5uQMucwRKHamXSst3Sj/Giw==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} - is-docker@2.2.1: - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} - engines: {node: '>=8'} - hasBin: true + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-document.all@1.0.0: + resolution: {integrity: sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-obj@2.0.0: resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} engines: {node: '>=8'} @@ -2074,6 +4282,42 @@ packages: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} @@ -2081,6 +4325,9 @@ packages: isarray@1.0.0: resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + isbinaryfile@4.0.10: resolution: {integrity: sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==} engines: {node: '>= 8.0.0'} @@ -2107,11 +4354,34 @@ packages: isomorphic.js@0.2.5: resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==} + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + jake@10.9.4: resolution: {integrity: sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==} engines: {node: '>=10'} hasBin: true + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jimp-compact@0.16.1: + resolution: {integrity: sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==} + jiti@2.7.0: resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==} hasBin: true @@ -2123,18 +4393,36 @@ packages: resolution: {integrity: sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==} hasBin: true + jsc-safe-url@0.2.4: + resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json-schema-typed@7.0.3: resolution: {integrity: sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-stringify-safe@5.0.1: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -2146,6 +4434,10 @@ packages: jsonfile@6.2.1: resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2157,9 +4449,21 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + lan-network@0.2.1: + resolution: {integrity: sha512-ONPnazC96VKDntab9j9JKwIWhZ4ZUceB4A9Epu4Ssg0hYFmtHZSeQ+n15nIwTFmcBUKtExOer8WTJ4GF9MO64A==} + hasBin: true + lazy-val@1.0.5: resolution: {integrity: sha512-0/BnGCCfyUMkBpeDgWihanIAF9JmZhHBgUhEqzvf+adhNGLoP6TaiI5oF8oyb3I45P+PcnrqihSf01M0l0G5+Q==} + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + lexical@0.41.0: resolution: {integrity: sha512-pNIm5+n+hVnJHB9gYPDYsIO5Y59dNaDU9rJmPPsfqQhP2ojKFnUoPbcRnrI9FJLXB14sSumcY8LUw7Sq70TZqA==} @@ -2168,6 +4472,9 @@ packages: engines: {node: '>=16'} hasBin: true + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -2246,6 +4553,13 @@ packages: resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} engines: {node: '>=6'} + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + lodash.escaperegexp@4.1.2: resolution: {integrity: sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw==} @@ -2253,20 +4567,40 @@ packages: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@2.2.0: + resolution: {integrity: sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==} + engines: {node: '>=4'} + longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lowercase-keys@2.0.0: resolution: {integrity: sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==} engines: {node: '>=8'} + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -2283,9 +4617,15 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + markdown-table@3.0.4: resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + matcher@3.0.0: resolution: {integrity: sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==} engines: {node: '>=10'} @@ -2342,6 +4682,73 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + metro-babel-transformer@0.84.4: + resolution: {integrity: sha512-rvCfz8snl9h20VcvpOHxZuHP1SlAkv4HXbzw7nyyVwu6Eqo5PRerbakQ9XmUCOsRy70spJ37O+G1TK8oMzo48g==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache-key@0.84.4: + resolution: {integrity: sha512-wVO79aGrkYImpnaVS4+d5RrRBRPX31QtvKB3wKGBuiNSznduZTQHzsrJZRroFJSwnygrzdsGUtDQPuqqFjFdvw==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-cache@0.84.4: + resolution: {integrity: sha512-gpcFQdSLUwUCk71saKoE64jLFbx2nwTfVCcPSULMNT8QYq0p1eZZE29Jvd0HtT/UlhC3ZOutLxJME5xqD2JUZg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-config@0.84.4: + resolution: {integrity: sha512-PMotGDjXcXLWo2TMRH+VR99phFNgYTwqh4OoieIKK3yTJa1Jmkl+fZJxDO0jfBvNF+WESHciHvpNuBtXaF3B0Q==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-core@0.84.4: + resolution: {integrity: sha512-HONpWC5LGXZn3ffkd4Hu6AIrfE7j4Z0g0wMo/goV24WOB3lhuFZ40KgvaDiSw8iyQHloMYay5N/wPX+z8oN/PQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-file-map@0.84.4: + resolution: {integrity: sha512-KSVDi/u60hKPx++NLu3MTIvyjzNoJnFAF8PQFxaj1jiSka/wjw+Ua6sNuJ0TDHQv+7AAoFQxeMgaRAe8Yic5wQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-minify-terser@0.84.4: + resolution: {integrity: sha512-5qpbaVOMC7CPitIpuewzVeGw7E+C3ykbv2mqTjQLl85Z3annSVGlSCTcsZjqXZzjupfK4Ztj3dDc4kc44NZwtQ==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-resolver@0.84.4: + resolution: {integrity: sha512-1qLgbxQ5ZGhhutuPot1Yp348ofDsATL2WkrHF65TobqTT9K3P9qJXw38bomk7ncp5B7OYMfWwtyBZo1lCV792A==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-runtime@0.84.4: + resolution: {integrity: sha512-Jibypds4g7AhzdRKY+kDoj51s5EXMwgyp5ddtlreDAsWefMdOx+agWqgm0H2XSZ/ueanHHVM89fnf5OJnlxa8Q==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-source-map@0.84.4: + resolution: {integrity: sha512-jbWkPxIesVuo1IWkvezmMJld6iu8nD62GsrZiV6jP37AOdbo4OBq1FJ+qkOg8sV05wAHB//jAbziuW0SlJfW4g==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-symbolicate@0.84.4: + resolution: {integrity: sha512-OnfpacxUqGPZQ27t8qK9mFa7uqHIlVWeqRqkCbvMvreEBiamEeOn8krKtcwgP5M4cYDPwuSmCTopHMVthqG4zA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + + metro-transform-plugins@0.84.4: + resolution: {integrity: sha512-kehr6HbAecqD0/a3xLXobELdPaAmRAl8bel0qagPF4vhZtux93nS8S4eq2kgKt6J2GnQpVjSoW1PXdst04mwow==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro-transform-worker@0.84.4: + resolution: {integrity: sha512-W1IYMvvXTu4MxYr7d9h7CeG2vpIr3bmLLIavkPY4O1ilzDrvS8z/NEe6y+pC44Ff7raMXQgYSfdqDUwN/i39gg==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + metro@0.84.4: + resolution: {integrity: sha512-8ETTubqfD6ornDy2zYDvRcKnVDOXdFJsjetYDBsY4oAsb6NJkiwFR+FaMESyGppFmQUyBQA4H4sFGxzcQSGtFA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2426,19 +4833,40 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + mime-types@2.1.35: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + mime@2.6.0: resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} engines: {node: '>=4.0.0'} hasBin: true + mimic-fn@1.2.0: + resolution: {integrity: sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==} + engines: {node: '>=4'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -2455,6 +4883,10 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimatch@10.2.5: resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} engines: {node: 18 || 20 || >=22} @@ -2485,23 +4917,54 @@ packages: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + mrmime@2.0.1: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + multitars@1.0.0: + resolution: {integrity: sha512-H/J4fMLedtudftaYMOg7ajzLYgT3/rwbWVJbqr/iUgB8DQztn38ys5HOqI1CzSxx8QhXXwOOnnBvd4v3jG5+Mg==} + nanoid@3.3.15: resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + native-run@2.0.3: resolution: {integrity: sha512-U1PllBuzW5d1gfan+88L+Hky2eZx+9gv3Pf6rNBxKbORxi7boHzqiA6QFGSnqMem4j0A9tZ08NMIs5+0m/VS1Q==} engines: {node: '>=16.0.0'} hasBin: true + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + node-abi@4.33.0: resolution: {integrity: sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==} engines: {node: '>=22.12.0'} @@ -2509,6 +4972,23 @@ packages: node-api-version@0.2.1: resolution: {integrity: sha512-2xP/IGGMmmSQpI1+O/k72jF/ykvZ89JeuKX3TLJAYPDVLUalrshrLHkeVcCCZqG/eEa635cr8IBYzgnDvM2O8Q==} + node-exports-info@1.6.2: + resolution: {integrity: sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==} + engines: {node: '>= 0.4'} + + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + engines: {node: '>= 6.13.0'} + node-gyp@12.4.0: resolution: {integrity: sha512-OMcPNvqTCFUnNaBlmdgq+lfNqY7gTiSmNRDjY3uAXRyudeKZEZxu3CLtjMQrx4zZxCX2b/mpNqTtwuCJgXhHkw==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2517,6 +4997,10 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + node-releases@2.0.51: + resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} + engines: {node: '>=18'} + nopt@9.0.0: resolution: {integrity: sha512-Zhq3a+yFKrYwSBluL4H9XP3m3y5uvQkB/09CwDruCiRmR/UJYnn9W4R48ry0uGC70aeTPKLynBtscP9efFFcPw==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2526,25 +5010,96 @@ packages: resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} engines: {node: '>=10'} + npm-package-arg@11.0.3: + resolution: {integrity: sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==} + engines: {node: ^16.14.0 || >=18.0.0} + + nullthrows@1.1.1: + resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + + ob1@0.84.4: + resolution: {integrity: sha512-eJXMpz4aQHXF/YBB9ddqZDIS+ooO91hObo9FoW/xBkr54/zCwYYCDqT/O54vNo8kOkWs5Ou/y28NgdrV0edQNA==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + obug@2.1.3: resolution: {integrity: sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==} engines: {node: '>=12.20.0'} + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + onetime@2.0.1: + resolution: {integrity: sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==} + engines: {node: '>=4'} + onetime@5.1.2: resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} engines: {node: '>=6'} + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@3.4.0: + resolution: {integrity: sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==} + engines: {node: '>=6'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + oxfmt@0.57.0: resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2591,6 +5146,10 @@ packages: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} engines: {node: '>=6'} + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -2598,16 +5157,32 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-png@2.1.0: + resolution: {integrity: sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==} + engines: {node: '>=10'} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@3.0.0: resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} engines: {node: '>=4'} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -2616,6 +5191,9 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-scurry@2.0.2: resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} engines: {node: 18 || 20 || >=22} @@ -2633,6 +5211,10 @@ packages: picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + picomatch@4.0.5: resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} @@ -2659,10 +5241,21 @@ packages: resolution: {integrity: sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==} engines: {node: '>=10.4.0'} + pngjs@3.4.0: + resolution: {integrity: sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==} + engines: {node: '>=4.0.0'} + pngjs@7.0.0: resolution: {integrity: sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==} engines: {node: '>=14.19.0'} + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + postcss@8.5.16: resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} engines: {node: ^10 || ^12 || >=14} @@ -2672,14 +5265,26 @@ packages: engines: {node: '>=14.0.0'} hasBin: true + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + pretty-format@27.5.1: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + prismjs@1.30.0: resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} engines: {node: '>=6'} + proc-log@4.2.0: + resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + proc-log@6.1.0: resolution: {integrity: sha512-iG+GYldRf2BQ0UDUAd6JQ/RwzaQy6mXmsk/IzlYyal4A4SNFw54MeH4/tLkF4I5WoWG9SQwuqWzS99jaFQHBuQ==} engines: {node: ^20.17.0 || >=22.9.0} @@ -2695,10 +5300,19 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} + promise@7.3.1: + resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} + + promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proper-lockfile@4.1.2: resolution: {integrity: sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==} @@ -2708,6 +5322,10 @@ packages: pump@3.0.4: resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + pvtsutils@1.3.6: resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==} @@ -2720,10 +5338,29 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + query-string@7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + + queue@6.0.2: + resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + react-devtools-core@6.1.5: + resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==} + + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + peerDependencies: + react: ^19.2.3 + react-dom@19.2.6: resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} peerDependencies: @@ -2734,35 +5371,191 @@ packages: peerDependencies: react: ^18.0.0 || ^19.0.0 + react-fast-compare@3.2.2: + resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + + react-freeze@1.0.4: + resolution: {integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==} + engines: {node: '>=10'} + peerDependencies: + react: '>=17.0.0' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-is@19.2.7: + resolution: {integrity: sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==} + react-markdown@10.1.0: resolution: {integrity: sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==} peerDependencies: '@types/react': '>=18' react: '>=18' - react@19.2.6: - resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} - engines: {node: '>=0.10.0'} - - read-binary-file-arch@1.0.6: - resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} - hasBin: true + react-native-drawer-layout@4.2.8: + resolution: {integrity: sha512-IswXoXEaQAnzUGo7tJyMPHP0qyUc/7zufpG+kpiStYegHpKQBBnlQoRdXkL1HbCa2o1ROWDhCSJ8XS0V4LNcjQ==} + peerDependencies: + react: '>= 18.2.0' + react-native: '*' + react-native-gesture-handler: '>= 2.0.0' + react-native-reanimated: '>= 2.0.0' - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + react-native-gesture-handler@2.32.0: + resolution: {integrity: sha512-uYIMOKlKENORq2SABE+jIjbPU+h5I/sQKcq2v16zRq848nwEp1fWRVwML4QWqijc8UcXJC25o54S8GQd4Mf2OA==} + peerDependencies: + react: '*' + react-native: '*' - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} + react-native-is-edge-to-edge@1.3.1: + resolution: {integrity: sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==} + peerDependencies: + react: '*' + react-native: '*' - rehype-raw@7.0.0: - resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + react-native-reanimated@4.5.0: + resolution: {integrity: sha512-+iPfvK34PKKYP/p/4TaBliFkbfvjGDIvXuiiaxvISP5ip7sWegvlacwU/uAV6zNDSSmX0tDyER7PurPMKGDipA==} + peerDependencies: + react: '*' + react-native: 0.83 - 0.86 + react-native-worklets: 0.10.x - rehype-sanitize@6.0.0: - resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} + react-native-safe-area-context@5.7.0: + resolution: {integrity: sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-screens@4.25.2: + resolution: {integrity: sha512-1Nj1fusFd+rIMKU/qC9yGKVG+3ofh11d3OdBQKL1iVvQfKvcB8vhvTGQf2TkfxW3bamxN+hCZIXmNuU0mRkyDg==} + peerDependencies: + react: '*' + react-native: '>=0.82.0' + + react-native-web@0.21.2: + resolution: {integrity: sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + react-native-worklets@0.10.0: + resolution: {integrity: sha512-JhE6IxDf6iabC0qu3+TAKA4v9RlluXmoIngPQX7/QUByf75lfrsHZ6/dQhyjEWnp1EEQiwzz8Cpew140ZcewDw==} + peerDependencies: + '@babel/core': '*' + '@react-native/metro-config': '*' + react: '*' + react-native: 0.83 - 0.86 + + react-native@0.86.0: + resolution: {integrity: sha512-17ALh/dd6AO4pgOVmOO5Axll5PbErEo3XFyLokyzW6usyi+OShIEPwUW26wLPlhVifgSOIfECCH0WN+0IqtJ1w==} + engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + hasBin: true + peerDependencies: + '@react-native/jest-preset': 0.86.0 + '@types/react': ^19.1.1 + react: ^19.2.3 + peerDependenciesMeta: + '@react-native/jest-preset': + optional: true + '@types/react': + optional: true + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + read-binary-file-arch@1.0.6: + resolution: {integrity: sha512-BNg9EN3DD3GsDXX7Aa8O4p92sryjkmzYYgmgTAc6CA4uGLEDzFfxOxugu21akOxpcXHiEgsYkC6nPsQvLLLmEg==} + hasBin: true + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.2: + resolution: {integrity: sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==} + hasBin: true + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-sanitize@6.0.0: + resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} remark-breaks@4.0.0: resolution: {integrity: sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==} @@ -2797,9 +5590,37 @@ packages: resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve-workspace-root@2.0.1: + resolution: {integrity: sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==} + + resolve@1.22.12: + resolution: {integrity: sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.7: + resolution: {integrity: sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==} + engines: {node: '>= 0.4'} + hasBin: true + responselike@2.0.1: resolution: {integrity: sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==} + restore-cursor@2.0.0: + resolution: {integrity: sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==} + engines: {node: '>=4'} + retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} @@ -2818,9 +5639,24 @@ packages: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} + safe-array-concat@1.1.4: + resolution: {integrity: sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==} + engines: {node: '>=0.4'} + safe-buffer@5.1.2: resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + sanitize-filename@1.6.4: resolution: {integrity: sha512-9ZyI08PsvdQl2r/bBIGubpVdR3RR9sY6RDiWFPreA21C/EFlQhmgo20UZlNjZMMZNubusLhAQozkA0Od5J21Eg==} @@ -2855,6 +5691,14 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serialize-error@2.1.0: + resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} + engines: {node: '>=0.10.0'} + serialize-error@7.0.1: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} @@ -2869,6 +5713,38 @@ packages: resolution: {integrity: sha512-bSjOuPcwPKLSJNhr9+bZxA20nQxVle5J5MNsYRVE6cIg7KpRLXGupymePavu0jrxlPiPsr4xGZSB8yUY2sH2sw==} engines: {node: '>=10'} + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + server-only@0.0.1: + resolution: {integrity: sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sf-symbols-typescript@2.2.0: + resolution: {integrity: sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==} + engines: {node: '>=10'} + + shallowequal@1.1.0: + resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -2877,12 +5753,38 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.10.0: + resolution: {integrity: sha512-w1aiOKwKuRgtwAReIIj89puqg+I7GvX4IbLrvmhXbzQsj1+Zwi4VO3+fa6ZF91TWSjIxoEkKnMeHcLEODK5ZXA==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.1: + resolution: {integrity: sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.1: + resolution: {integrity: sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==} + engines: {node: '>= 0.4'} + siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + simple-plist@1.3.1: + resolution: {integrity: sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -2898,6 +5800,10 @@ packages: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} + slugify@1.6.9: + resolution: {integrity: sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==} + engines: {node: '>=8.0.0'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2905,6 +5811,10 @@ packages: source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} @@ -2912,6 +5822,10 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + split2@4.2.0: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} @@ -2919,44 +5833,134 @@ packages: sprintf-js@1.1.3: resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==} + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + + standard-navigation@0.0.5: + resolution: {integrity: sha512-YAmzwAiiQVocZxO/VGPFiQHcu5pKiz09QIGC0MK6aRMoa3E0QkoTQgcqJr7ZZ3OMiNhu4DkaGElFI5htjOIDbw==} + stat-mode@1.0.0: resolution: {integrity: sha512-jH9EhtKIjuXZ2cWxmXS8ZP80XyC3iasQxMDV8jzhNJpfDb7VbQLVW4Wvsxz9QZvzV+G4YoSfBUVKDOyxLzi/sg==} engines: {node: '>= 6'} + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@4.2.0: resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + stream-buffers@2.2.0: + resolution: {integrity: sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==} + engines: {node: '>= 0.10.0'} + + strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.11: + resolution: {integrity: sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.10: + resolution: {integrity: sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + string_decoder@1.1.1: resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + structured-headers@0.4.1: + resolution: {integrity: sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==} + style-to-js@1.1.21: resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} style-to-object@1.0.14: resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + styleq@0.1.3: + resolution: {integrity: sha512-3ZUifmCDCQanjeej1f6kyl/BeP/Vae5EYkQ9iJfUm/QwZvlgnZzyflqAsAWYURdtea8Vkvswu2GrC57h3qffcA==} + sumchecker@3.0.1: resolution: {integrity: sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==} engines: {node: '>= 8.0'} + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-hyperlinks@2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + tabbable@6.5.0: resolution: {integrity: sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA==} @@ -2981,6 +5985,18 @@ packages: resolution: {integrity: sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==} engines: {node: '>=6.0.0'} + terminal-link@2.1.1: + resolution: {integrity: sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==} + engines: {node: '>=8'} + + terser@5.49.0: + resolution: {integrity: sha512-SNiDnXyHSrxVcIOtVbULzcTmniUiwcV7Nwdyj1twVubeTmbjoa8p69KKDpfkdoOavuM4/GRm1+ykI8qqnavHoA==} + engines: {node: '>=10'} + hasBin: true + + throat@5.0.0: + resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + through2@4.0.2: resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} @@ -3016,10 +6032,27 @@ packages: resolution: {integrity: sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==} engines: {node: '>=14.14'} + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + toqr@0.1.1: + resolution: {integrity: sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==} + totalist@3.0.1: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -3033,22 +6066,67 @@ packages: truncate-utf8-bytes@1.0.2: resolution: {integrity: sha512-95Pu1QXQvruGEhv62XCMO3Mm90GscOCClvrIUwCM0PYOXK3kaF3l3sIHxx71ThJfcbM2O5Au6SO3AWCSEfW4mQ==} + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + type-fest@0.13.1: resolution: {integrity: sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==} engines: {node: '>=10'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + type-fest@2.19.0: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} + engines: {node: '>= 0.4'} + typescript@6.0.3: resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true + ua-parser-js@1.0.41: + resolution: {integrity: sha512-LbBDqdIC5s8iROCUjMbW1f5dJQTEFB1+KO9ogbvlb3nm9n4YHa5p4KTvFPWvh2Hs8gZMBuiB1/8+pdfe/tDPug==} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} @@ -3059,6 +6137,22 @@ packages: resolution: {integrity: sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==} engines: {node: '>=18.17'} + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -3085,6 +6179,13 @@ packages: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + unrs-resolver@1.12.2: + resolution: {integrity: sha512-dmlRxBJJayXjqTwC+JtF1HhJmgf3ftQ3YejFcZrf4+KKtJv0qDsK1pjqaaVjG7wJ5NJ6UVP1OqRMQ71Z4C3rxQ==} + untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -3092,6 +6193,40 @@ packages: unzipper@0.12.5: resolution: {integrity: sha512-tXYOi9R57Uj/2Z25SOs5RRSzq886MBQj2gY8dPL+xl/kv6s6SvByoKfAtvfVeEuhntWDgjd2o9p2lb4TVPAz0A==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-latest-callback@0.2.6: + resolution: {integrity: sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==} + peerDependencies: + react: '>=16.8' + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + use-sync-external-store@1.6.0: resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: @@ -3103,6 +6238,29 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@7.0.3: + resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). + hasBin: true + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vaul@1.1.2: + resolution: {integrity: sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==} + peerDependencies: + react: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc + vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -3166,24 +6324,64 @@ packages: jsdom: optional: true + vlq@1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + warn-once@0.1.1: + resolution: {integrity: sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} webcrypto-core@1.9.2: resolution: {integrity: sha512-gsXecm82UQNlTBURJGuqOWy1Ww08S3kZUcr3aOJS02Pk0xLtkfeUAVC0u0xhgdonFme80edSJUIJyuvL/7250Q==} - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - which@5.0.0: - resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} - engines: {node: ^18.17.0 || >=20.5.0} - hasBin: true + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} - which@6.0.1: - resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} + whatwg-url-minimum@0.1.2: + resolution: {integrity: sha512-XPEm0XFQWNVG292lII1PrRRJl3sItrs7CettZ4ncYxuDVpLyy+NwlGyut2hXI0JswcJUxeCH+CyOJK0ZzAXD6A==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.22: + resolution: {integrity: sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@5.0.0: + resolution: {integrity: sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==} + engines: {node: ^18.17.0 || >=20.5.0} + hasBin: true + + which@6.0.1: + resolution: {integrity: sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg==} engines: {node: ^20.17.0 || >=22.9.0} hasBin: true @@ -3192,6 +6390,10 @@ packages: engines: {node: '>=8'} hasBin: true + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3199,6 +6401,18 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + ws@7.5.13: + resolution: {integrity: sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + ws@8.18.3: resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} engines: {node: '>=10.0.0'} @@ -3223,6 +6437,14 @@ packages: utf-8-validate: optional: true + xcode@3.0.1: + resolution: {integrity: sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==} + engines: {node: '>=10.0.0'} + + xml2js@0.6.0: + resolution: {integrity: sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==} + engines: {node: '>=4.0.0'} + xml2js@0.6.2: resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} engines: {node: '>=4.0.0'} @@ -3239,6 +6461,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} @@ -3246,6 +6471,11 @@ packages: resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} engines: {node: '>=18'} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -3265,6 +6495,15 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + zustand@5.0.14: resolution: {integrity: sha512-/8tAspM5LMPr28b3fwLYrtdj77ECpfZviaP75CMTnwO8ISyaE4GDIG/9rDDYq/cH9D2Xw2A2RXglLInmVBQB/g==} engines: {node: '>=12.20.0'} @@ -3288,16 +6527,492 @@ packages: snapshots: + '@adobe/css-tools@4.5.0': {} + '@babel/code-frame@7.29.7': dependencies: '@babel/helper-validator-identifier': 7.29.7 js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.6 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/traverse': 7.29.7 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.8(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.12 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-member-expression-to-functions@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-remap-async-to-generator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-wrap-function': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-member-expression-to-functions': 7.29.7 + '@babel/helper-optimise-call-expression': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helper-wrap-function@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-proposal-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-decorators': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-export-default-from@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-decorators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-export-default-from@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-flow@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-syntax-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-arrow-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-async-generator-functions@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-remap-async-to-generator': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoping@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-class-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-replace-supers': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-destructuring@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-export-namespace-from@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-flow-strip-types@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) + + '@babel/plugin-transform-for-of@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-logical-assignment-operators@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-modules-commonjs@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-nullish-coalescing-operator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-object-rest-spread@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-optional-chaining@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-private-methods@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-display-name@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-development@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-regenerator@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-runtime@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + babel-plugin-polyfill-corejs2: 0.4.17(@babel/core@7.29.7) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.29.7) + babel-plugin-polyfill-regenerator: 0.6.8(@babel/core@7.29.7) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-template-literals@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-annotate-as-pure': 7.29.7 + '@babel/helper-create-class-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-skip-transparent-expression-wrappers': 7.29.7 + '@babel/plugin-syntax-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-regex@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-create-regexp-features-plugin': 7.29.7(@babel/core@7.29.7) + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/preset-typescript@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + '@babel/plugin-syntax-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + '@babel/runtime@7.29.7': {} + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@base-ui/react@1.6.0(@types/react@19.2.17)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': dependencies: '@babel/runtime': 7.29.7 @@ -3353,6 +7068,10 @@ snapshots: dependencies: tslib: 2.8.1 + '@egjs/hammerjs@2.0.17': + dependencies: + '@types/hammerjs': 2.0.46 + '@electron/asar@3.4.1': dependencies: commander: 5.1.0 @@ -3412,39 +7131,482 @@ snapshots: transitivePeerDependencies: - supports-color - '@electron/rebuild@4.2.0': + '@electron/rebuild@4.2.0': + dependencies: + '@malept/cross-spawn-promise': 2.0.0 + debug: 4.4.3 + node-abi: 4.33.0 + node-api-version: 0.2.1 + node-gyp: 12.4.0 + read-binary-file-arch: 1.0.6 + transitivePeerDependencies: + - supports-color + + '@electron/universal@2.0.3': + dependencies: + '@electron/asar': 3.4.1 + '@malept/cross-spawn-promise': 2.0.0 + debug: 4.4.3 + dir-compare: 4.2.0 + fs-extra: 11.3.6 + minimatch: 9.0.9 + plist: 3.1.0 + transitivePeerDependencies: + - supports-color + + '@electron/windows-sign@1.2.2': + dependencies: + cross-dirname: 0.1.0 + debug: 4.4.3 + fs-extra: 11.3.6 + minimist: 1.2.8 + postject: 1.0.0-alpha.6 + transitivePeerDependencies: + - supports-color + optional: true + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.5(jiti@2.7.0))': + dependencies: + eslint: 9.39.5(jiti@2.7.0) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.6': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.3.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.5': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@expo-google-fonts/material-symbols@0.4.41': {} + + '@expo/cli@57.0.9(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-constants@57.0.6)(expo-font@57.0.1)(expo-router@57.0.7)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3)': + dependencies: + '@expo/code-signing-certificates': 0.0.6 + '@expo/config': 57.0.5(typescript@6.0.3) + '@expo/config-plugins': 57.0.5(typescript@6.0.3) + '@expo/devcert': 1.2.1 + '@expo/env': 2.4.2 + '@expo/image-utils': 0.11.3(typescript@6.0.3) + '@expo/inline-modules': 0.1.3(typescript@6.0.3) + '@expo/json-file': 11.0.1 + '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/metro': 56.0.0 + '@expo/metro-config': 57.0.6(expo@57.0.7)(typescript@6.0.3) + '@expo/metro-file-map': 57.0.1 + '@expo/osascript': 2.7.1 + '@expo/package-manager': 1.13.1 + '@expo/plist': 0.8.1 + '@expo/prebuild-config': 57.0.8(typescript@6.0.3) + '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/router-server': 57.0.3(@expo/metro-runtime@57.0.6)(expo-constants@57.0.6)(expo-font@57.0.1)(expo-router@57.0.7)(expo-server@57.0.1)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@expo/schema-utils': 57.0.2 + '@expo/spawn-async': 1.8.0 + '@expo/ws-tunnel': 2.0.0(ws@8.18.3) + '@expo/xcpretty': 4.4.4 + '@react-native/dev-middleware': 0.86.0 + accepts: 1.3.8 + agent-cli-detector: 0.1.3 + arg: 5.0.2 + bplist-creator: 0.1.0 + bplist-parser: 0.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + compression: 1.8.1 + connect: 3.7.0 + debug: 4.4.3 + dnssd-advertise: 1.1.6 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-server: 57.0.1 + fetch-nodeshim: 0.4.10 + getenv: 2.0.0 + glob: 13.0.6 + lan-network: 0.2.1 + multitars: 1.0.0 + node-forge: 1.4.0 + npm-package-arg: 11.0.3 + ora: 3.4.0 + picomatch: 4.0.5 + pretty-format: 29.7.0 + progress: 2.0.3 + prompts: 2.4.2 + resolve-from: 5.0.0 + semver: 7.8.5 + send: 0.19.2 + slugify: 1.6.9 + stacktrace-parser: 0.1.11 + structured-headers: 0.4.1 + terminal-link: 2.1.1 + toqr: 0.1.1 + wrap-ansi: 7.0.0 + ws: 8.18.3 + zod: 3.25.76 + optionalDependencies: + expo-router: 57.0.7(8d7c44b937c34bc9b52672f8f7e374b1) + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + transitivePeerDependencies: + - '@expo/dom-webview' + - '@expo/metro-runtime' + - bufferutil + - expo-constants + - expo-font + - react + - react-dom + - react-server-dom-webpack + - supports-color + - typescript + - utf-8-validate + + '@expo/code-signing-certificates@0.0.6': + dependencies: + node-forge: 1.4.0 + + '@expo/config-plugins@57.0.5(typescript@6.0.3)': + dependencies: + '@expo/config-types': 57.0.2 + '@expo/json-file': 11.0.1 + '@expo/plist': 0.8.1 + '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/sdk-runtime-versions': 1.0.0 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.6 + semver: 7.8.5 + slugify: 1.6.9 + xcode: 3.0.1 + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/config-types@57.0.2': {} + + '@expo/config@57.0.5(typescript@6.0.3)': + dependencies: + '@expo/config-plugins': 57.0.5(typescript@6.0.3) + '@expo/config-types': 57.0.2 + '@expo/json-file': 11.0.1 + '@expo/require-utils': 57.0.3(typescript@6.0.3) + deepmerge: 4.3.1 + getenv: 2.0.0 + glob: 13.0.6 + resolve-workspace-root: 2.0.1 + semver: 7.8.5 + slugify: 1.6.9 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/devcert@1.2.1': + dependencies: + '@expo/sudo-prompt': 9.3.2 + debug: 3.2.7 + transitivePeerDependencies: + - supports-color + + '@expo/devtools@57.0.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + dependencies: + chalk: 4.1.2 + optionalDependencies: + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + '@expo/dom-webview@57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + '@expo/env@2.4.2': + dependencies: + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + transitivePeerDependencies: + - supports-color + + '@expo/expo-modules-macros-plugin@0.6.1': {} + + '@expo/fingerprint@0.20.5': + dependencies: + '@expo/env': 2.4.2 + '@expo/spawn-async': 1.8.0 + arg: 5.0.2 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.6 + ignore: 5.3.2 + minimatch: 10.2.5 + resolve-from: 5.0.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + + '@expo/image-utils@0.11.3(typescript@6.0.3)': + dependencies: + '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + getenv: 2.0.0 + jimp-compact: 0.16.1 + parse-png: 2.1.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/inline-modules@0.1.3(typescript@6.0.3)': + dependencies: + '@expo/config-plugins': 57.0.5(typescript@6.0.3) + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/json-file@11.0.1': + dependencies: + '@babel/code-frame': 7.29.7 + json5: 2.2.3 + + '@expo/local-build-cache-provider@57.0.4(typescript@6.0.3)': + dependencies: + '@expo/config': 57.0.5(typescript@6.0.3) + chalk: 4.1.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/log-box@57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + dependencies: + '@expo/dom-webview': 57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + anser: 1.4.10 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + stacktrace-parser: 0.1.11 + + '@expo/metro-config@57.0.6(expo@57.0.7)(typescript@6.0.3)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@expo/config': 57.0.5(typescript@6.0.3) + '@expo/env': 2.4.2 + '@expo/json-file': 11.0.1 + '@expo/metro': 56.0.0 + '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/spawn-async': 1.8.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/remapping': 2.3.5 + '@jridgewell/sourcemap-codec': 1.5.5 + browserslist: 4.28.6 + chalk: 4.1.2 + debug: 4.4.3 + getenv: 2.0.0 + glob: 13.0.6 + hermes-parser: 0.36.1 + jsc-safe-url: 0.2.4 + lightningcss: 1.32.0 + picomatch: 4.0.5 + postcss: 8.5.16 + resolve-from: 5.0.0 + optionalDependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + transitivePeerDependencies: + - bufferutil + - supports-color + - typescript + - utf-8-validate + + '@expo/metro-file-map@57.0.1': + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + '@expo/metro-runtime@57.0.6(@expo/log-box@57.0.1)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + dependencies: + '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + anser: 1.4.10 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + pretty-format: 29.7.0 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + stacktrace-parser: 0.1.11 + whatwg-fetch: 3.6.20 + optionalDependencies: + react-dom: 19.2.3(react@19.2.3) + + '@expo/metro@56.0.0': + dependencies: + metro: 0.84.4 + metro-babel-transformer: 0.84.4 + metro-cache: 0.84.4 + metro-cache-key: 0.84.4 + metro-config: 0.84.4 + metro-core: 0.84.4 + metro-file-map: 0.84.4 + metro-minify-terser: 0.84.4 + metro-resolver: 0.84.4 + metro-runtime: 0.84.4 + metro-source-map: 0.84.4 + metro-symbolicate: 0.84.4 + metro-transform-plugins: 0.84.4 + metro-transform-worker: 0.84.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@expo/osascript@2.7.1': + dependencies: + '@expo/spawn-async': 1.8.0 + + '@expo/package-manager@1.13.1': + dependencies: + '@expo/json-file': 11.0.1 + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + npm-package-arg: 11.0.3 + ora: 3.4.0 + resolve-workspace-root: 2.0.1 + + '@expo/plist@0.8.1': + dependencies: + '@xmldom/xmldom': 0.8.13 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + + '@expo/prebuild-config@57.0.8(typescript@6.0.3)': + dependencies: + '@expo/config': 57.0.5(typescript@6.0.3) + '@expo/config-plugins': 57.0.5(typescript@6.0.3) + '@expo/config-types': 57.0.2 + '@expo/image-utils': 0.11.3(typescript@6.0.3) + '@expo/json-file': 11.0.1 + '@react-native/normalize-colors': 0.86.0 + debug: 4.4.3 + expo-modules-autolinking: 57.0.8(typescript@6.0.3) + resolve-from: 5.0.0 + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + - typescript + + '@expo/require-utils@57.0.3(typescript@6.0.3)': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + optionalDependencies: + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@expo/router-server@57.0.3(@expo/metro-runtime@57.0.6)(expo-constants@57.0.6)(expo-font@57.0.1)(expo-router@57.0.7)(expo-server@57.0.1)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@malept/cross-spawn-promise': 2.0.0 debug: 4.4.3 - node-abi: 4.33.0 - node-api-version: 0.2.1 - node-gyp: 12.4.0 - read-binary-file-arch: 1.0.6 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-constants: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + expo-font: 57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + expo-server: 57.0.1 + react: 19.2.3 + optionalDependencies: + '@expo/metro-runtime': 57.0.6(@expo/log-box@57.0.1)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + expo-router: 57.0.7(8d7c44b937c34bc9b52672f8f7e374b1) + react-dom: 19.2.3(react@19.2.3) transitivePeerDependencies: - supports-color - '@electron/universal@2.0.3': + '@expo/schema-utils@57.0.2': {} + + '@expo/sdk-runtime-versions@1.0.0': {} + + '@expo/spawn-async@1.8.0': dependencies: - '@electron/asar': 3.4.1 - '@malept/cross-spawn-promise': 2.0.0 - debug: 4.4.3 - dir-compare: 4.2.0 - fs-extra: 11.3.6 - minimatch: 9.0.9 - plist: 3.1.0 - transitivePeerDependencies: - - supports-color + cross-spawn: 7.0.6 - '@electron/windows-sign@1.2.2': + '@expo/sudo-prompt@9.3.2': {} + + '@expo/ui@57.0.7(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': dependencies: - cross-dirname: 0.1.0 - debug: 4.4.3 - fs-extra: 11.3.6 - minimist: 1.2.8 - postject: 1.0.0-alpha.6 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + sf-symbols-typescript: 2.2.0 + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + optionalDependencies: + '@babel/core': 7.29.7 + react-dom: 19.2.3(react@19.2.3) + react-native-worklets: 0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) transitivePeerDependencies: - - supports-color - optional: true + - '@types/react' + - '@types/react-dom' + + '@expo/ws-tunnel@2.0.0(ws@8.18.3)': + dependencies: + ws: 8.18.3 + + '@expo/xcpretty@4.4.4': + dependencies: + '@babel/code-frame': 7.29.7 + chalk: 4.1.2 + js-yaml: 4.3.0 '@floating-ui/core@1.7.5': dependencies: @@ -3475,6 +7637,22 @@ snapshots: '@fontsource/jetbrains-mono@5.2.8': {} + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + '@ionic/cli-framework-output@2.2.8': dependencies: '@ionic/utils-terminal': 2.3.5 @@ -3555,6 +7733,21 @@ snapshots: dependencies: minipass: 7.1.3 + '@isaacs/ttlcache@1.4.1': {} + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.12 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 26.1.1 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -3567,6 +7760,11 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.31': @@ -3753,10 +7951,19 @@ snapshots: transitivePeerDependencies: - supports-color + '@napi-rs/wasm-runtime@1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.3 + optional: true + '@noble/hashes@1.4.0': {} '@noble/hashes@2.2.0': {} + '@nolyfill/is-core-module@1.0.39': {} + '@oh-my-pi/app-wire@file:vendor/app-wire/oh-my-pi-app-wire-0.6.2.tgz': {} '@oxc-project/runtime@0.138.0': {} @@ -3927,8 +8134,352 @@ snapshots: '@preact/signals-core@1.14.4': {} + '@radix-ui/primitive@1.1.5': {} + + '@radix-ui/react-collection@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-compose-refs@1.1.3(@types/react@19.2.17)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-context@1.2.0(@types/react@19.2.17)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-dialog@1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-focus-guards': 1.1.4(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-focus-scope': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-portal': 1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.3) + aria-hidden: 1.2.6 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-remove-scroll: 2.7.2(@types/react@19.2.17)(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-direction@1.1.2(@types/react@19.2.17)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-dismissable-layer@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-focus-guards@1.1.4(@types/react@19.2.17)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-focus-scope@1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-id@1.1.2(@types/react@19.2.17)(react@19.2.3)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-portal@1.1.13(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-presence@1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-primitive@2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-roving-focus@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-collection': 1.1.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-callback-ref': 1.1.2(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-use-is-hydrated': 0.1.1(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-slot@1.3.0(@types/react@19.2.17)(react@19.2.3)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.3(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-tabs@1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@radix-ui/primitive': 1.1.5 + '@radix-ui/react-context': 1.2.0(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-direction': 1.1.2(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-id': 1.1.2(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-presence': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-primitive': 2.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-roving-focus': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@radix-ui/react-use-controllable-state': 1.2.3(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@types/react-dom': 19.2.3(@types/react@19.2.17) + + '@radix-ui/react-use-callback-ref@1.1.2(@types/react@19.2.17)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-controllable-state@1.2.3(@types/react@19.2.17)(react@19.2.3)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.3(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-effect-event@0.0.3(@types/react@19.2.17)(react@19.2.3)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.2(@types/react@19.2.17)(react@19.2.3) + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.17)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@radix-ui/react-use-layout-effect@1.1.2(@types/react@19.2.17)(react@19.2.3)': + dependencies: + react: 19.2.3 + optionalDependencies: + '@types/react': 19.2.17 + + '@react-native-masked-view/masked-view@0.3.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + dependencies: + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + '@react-native/assets-registry@0.86.0': {} + + '@react-native/babel-plugin-codegen@0.86.0(@babel/core@7.29.7)': + dependencies: + '@babel/traverse': 7.29.7 + '@react-native/codegen': 0.86.0(@babel/core@7.29.7) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@react-native/babel-preset@0.86.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-regenerator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) + '@react-native/babel-plugin-codegen': 0.86.0(@babel/core@7.29.7) + babel-plugin-syntax-hermes-parser: 0.36.0 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) + react-refresh: 0.14.2 + transitivePeerDependencies: + - supports-color + + '@react-native/codegen@0.86.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + hermes-parser: 0.36.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + tinyglobby: 0.2.17 + yargs: 17.7.3 + + '@react-native/community-cli-plugin@0.86.0(@react-native/metro-config@0.86.0(@babel/core@7.29.7))': + dependencies: + '@react-native/dev-middleware': 0.86.0 + debug: 4.4.3 + invariant: 2.2.4 + metro: 0.84.4 + metro-config: 0.84.4 + metro-core: 0.84.4 + semver: 7.8.5 + optionalDependencies: + '@react-native/metro-config': 0.86.0(@babel/core@7.29.7) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/debugger-frontend@0.86.0': {} + + '@react-native/debugger-shell@0.86.0': + dependencies: + cross-spawn: 7.0.6 + debug: 4.4.3 + fb-dotslash: 0.5.8 + transitivePeerDependencies: + - supports-color + + '@react-native/dev-middleware@0.86.0': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.86.0 + '@react-native/debugger-shell': 0.86.0 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 0.3.0 + connect: 3.7.0 + debug: 4.4.3 + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + serve-static: 1.16.3 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/gradle-plugin@0.86.0': {} + + '@react-native/js-polyfills@0.86.0': {} + + '@react-native/metro-babel-transformer@0.86.0(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@react-native/babel-preset': 0.86.0(@babel/core@7.29.7) + hermes-parser: 0.36.0 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + '@react-native/metro-config@0.86.0(@babel/core@7.29.7)': + dependencies: + '@react-native/js-polyfills': 0.86.0 + '@react-native/metro-babel-transformer': 0.86.0(@babel/core@7.29.7) + metro-config: 0.84.4 + metro-runtime: 0.84.4 + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/normalize-colors@0.74.89': {} + + '@react-native/normalize-colors@0.86.0': {} + + '@react-native/virtualized-lists@0.86.0(@types/react@19.2.17)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)': + dependencies: + invariant: 2.2.4 + nullthrows: 1.1.1 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + '@rolldown/pluginutils@1.0.1': {} + '@rtsao/scc@1.1.0': {} + + '@sinclair/typebox@0.27.12': {} + '@sindresorhus/is@4.6.0': {} '@standard-schema/spec@1.1.0': {} @@ -3998,12 +8549,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.3.2 '@tailwindcss/oxide-win32-x64-msvc': 4.3.2 - '@tailwindcss/vite@4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))': + '@tailwindcss/vite@4.3.2(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))': dependencies: '@tailwindcss/node': 4.3.2 '@tailwindcss/oxide': 4.3.2 tailwindcss: 4.3.2 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' '@tanstack/history@1.162.0': {} @@ -4043,10 +8594,24 @@ snapshots: picocolors: 1.1.1 pretty-format: 27.5.1 + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': dependencies: '@testing-library/dom': 10.4.1 + '@tybys/wasm-util@0.10.3': + dependencies: + tslib: 2.8.1 + optional: true + '@types/aria-query@5.0.4': {} '@types/cacheable-request@6.0.3': @@ -4081,12 +8646,28 @@ snapshots: dependencies: '@types/node': 26.1.1 + '@types/hammerjs@2.0.46': {} + '@types/hast@3.0.5': dependencies: '@types/unist': 3.0.3 '@types/http-cache-semantics@4.2.0': {} + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + '@types/keyv@3.1.4': dependencies: '@types/node': 26.1.1 @@ -4105,32 +8686,133 @@ snapshots: dependencies: undici-types: 8.3.0 - '@types/react-dom@19.2.3(@types/react@19.2.17)': + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react-test-renderer@19.1.0': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@types/responselike@1.0.3': + dependencies: + '@types/node': 26.1.1 + + '@types/slice-ansi@4.0.0': {} + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@types/ws@8.5.13': + dependencies: + '@types/node': 26.1.1 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 26.1.1 + optional: true + + '@typescript-eslint/eslint-plugin@8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/type-utils': 8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.64.0 + eslint: 9.39.5(jiti@2.7.0) + ignore: 7.0.6 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.64.0 + debug: 4.4.3 + eslint: 9.39.5(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.64.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@6.0.3) + '@typescript-eslint/types': 8.64.0 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.64.0': dependencies: - '@types/react': 19.2.17 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/visitor-keys': 8.64.0 - '@types/react@19.2.17': + '@typescript-eslint/tsconfig-utils@8.64.0(typescript@6.0.3)': dependencies: - csstype: 3.2.3 + typescript: 6.0.3 - '@types/responselike@1.0.3': + '@typescript-eslint/type-utils@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@types/node': 26.1.1 - - '@types/slice-ansi@4.0.0': {} + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + debug: 4.4.3 + eslint: 9.39.5(jiti@2.7.0) + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color - '@types/unist@2.0.11': {} + '@typescript-eslint/types@8.64.0': {} - '@types/unist@3.0.3': {} + '@typescript-eslint/typescript-estree@8.64.0(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.64.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.64.0(typescript@6.0.3) + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/visitor-keys': 8.64.0 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.5 + tinyglobby: 0.2.17 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color - '@types/ws@8.5.13': + '@typescript-eslint/utils@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3)': dependencies: - '@types/node': 26.1.1 + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) + '@typescript-eslint/scope-manager': 8.64.0 + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/typescript-estree': 8.64.0(typescript@6.0.3) + eslint: 9.39.5(jiti@2.7.0) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color - '@types/yauzl@2.10.3': + '@typescript-eslint/visitor-keys@8.64.0': dependencies: - '@types/node': 26.1.1 - optional: true + '@typescript-eslint/types': 8.64.0 + eslint-visitor-keys: 5.0.1 '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260604.1': optional: true @@ -4165,45 +8847,117 @@ snapshots: '@ungap/structured-clone@1.3.2': {} - '@vitejs/plugin-react@6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))': + '@unrs/resolver-binding-android-arm-eabi@1.12.2': + optional: true + + '@unrs/resolver-binding-android-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-loong64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.12.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.12.2': + optional: true + + '@unrs/resolver-binding-openharmony-arm64@1.12.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.12.2': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.6(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.12.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.12.2': + optional: true + + '@vitejs/plugin-react@6.0.3(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(babel-plugin-react-compiler@1.0.0)': dependencies: '@rolldown/pluginutils': 1.0.1 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' + optionalDependencies: + babel-plugin-react-compiler: 1.0.0 - '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9)': + '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9) + vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)))': + '@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9)': dependencies: '@testing-library/dom': 10.4.1 '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))) - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9) + vitest: 4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) transitivePeerDependencies: - bufferutil - msw - utf-8-validate - vite - '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9)': + '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) '@vitest/utils': 4.1.9 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -4211,16 +8965,16 @@ snapshots: - utf-8-validate - vite - '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)))': + '@vitest/browser@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9)': dependencies: '@blazediff/core': 1.9.1 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) '@vitest/utils': 4.1.9 magic-string: 0.30.21 pngjs: 7.0.0 sirv: 3.0.2 tinyrainbow: 3.1.0 - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + vitest: 4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) ws: 8.21.0 transitivePeerDependencies: - bufferutil @@ -4237,21 +8991,21 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))': + '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' - '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))': + '@vitest/mocker@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))': dependencies: '@vitest/spy': 4.1.9 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' '@vitest/pretty-format@4.1.9': dependencies: @@ -4277,7 +9031,7 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)': + '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)': dependencies: '@oxc-project/runtime': 0.138.0 '@oxc-project/types': 0.138.0 @@ -4287,9 +9041,11 @@ snapshots: '@types/node': 24.12.4 fsevents: 2.3.3 jiti: 2.7.0 + terser: 5.49.0 typescript: 6.0.3 + yaml: 2.9.0 - '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)': + '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)': dependencies: '@oxc-project/runtime': 0.138.0 '@oxc-project/types': 0.138.0 @@ -4299,7 +9055,9 @@ snapshots: '@types/node': 26.1.1 fsevents: 2.3.3 jiti: 2.7.0 + terser: 5.49.0 typescript: 6.0.3 + yaml: 2.9.0 '@voidzero-dev/vite-plus-darwin-arm64@0.2.2': optional: true @@ -4333,12 +9091,41 @@ snapshots: abbrev@4.0.0: {} + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn-jsx@5.3.2(acorn@8.17.0): + dependencies: + acorn: 8.17.0 + + acorn@8.17.0: {} + agent-base@7.1.4: {} + agent-cli-detector@0.1.3: {} + ajv-formats@2.1.1(ajv@8.20.0): optionalDependencies: ajv: 8.20.0 + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + ajv@8.20.0: dependencies: fast-deep-equal: 3.1.3 @@ -4346,8 +9133,20 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + anser@1.4.10: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-regex@4.1.1: {} + ansi-regex@5.0.1: {} + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -4402,12 +9201,87 @@ snapshots: transitivePeerDependencies: - supports-color + arg@5.0.2: {} + argparse@2.0.1: {} + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + aria-query@5.3.0: dependencies: dequal: 2.0.3 + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + asap@2.0.6: {} + asn1js@3.0.10: dependencies: pvtsutils: 1.3.6 @@ -4420,6 +9294,8 @@ snapshots: async-exit-hook@2.0.1: {} + async-function@1.0.0: {} + async@3.2.6: {} asynckit@0.4.0: {} @@ -4428,8 +9304,110 @@ snapshots: atomically@1.7.0: {} + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + aws4@1.13.2: {} + babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.7): + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + core-js-compat: 3.49.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.8(@babel/core@7.29.7): + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.7) + transitivePeerDependencies: + - supports-color + + babel-plugin-react-compiler@1.0.0: + dependencies: + '@babel/types': 7.29.7 + + babel-plugin-react-native-web@0.21.2: {} + + babel-plugin-syntax-hermes-parser@0.36.0: + dependencies: + hermes-parser: 0.36.0 + + babel-plugin-syntax-hermes-parser@0.36.1: + dependencies: + hermes-parser: 0.36.1 + + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.29.7): + dependencies: + '@babel/plugin-syntax-flow': 7.29.7(@babel/core@7.29.7) + transitivePeerDependencies: + - '@babel/core' + + babel-preset-expo@57.0.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.7)(react-refresh@0.14.2): + dependencies: + '@babel/generator': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/plugin-proposal-decorators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-proposal-export-default-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-export-default-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.7) + '@babel/plugin-transform-async-generator-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-async-to-generator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-block-scoping': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-static-block': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-destructuring': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-export-namespace-from': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-flow-strip-types': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-for-of': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-logical-assignment-operators': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-modules-commonjs': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-object-rest-spread': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-catch-binding': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-parameters': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-methods': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-private-property-in-object': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-display-name': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-development': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-pure-annotations': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-runtime': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@react-native/babel-plugin-codegen': 0.86.0(@babel/core@7.29.7) + babel-plugin-react-compiler: 1.0.0 + babel-plugin-react-native-web: 0.21.2 + babel-plugin-syntax-hermes-parser: 0.36.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.29.7) + debug: 4.4.3 + react-refresh: 0.14.2 + optionalDependencies: + '@babel/runtime': 7.29.7 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + badgin@1.2.3: {} + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -4438,6 +9416,8 @@ snapshots: base64-js@1.5.1: {} + baseline-browser-mapping@2.10.43: {} + big-integer@1.6.52: {} bluebird@3.7.2: {} @@ -4445,6 +9425,14 @@ snapshots: boolean@3.2.0: optional: true + bplist-creator@0.1.0: + dependencies: + stream-buffers: 2.2.0 + + bplist-parser@0.3.1: + dependencies: + big-integer: 1.6.52 + bplist-parser@0.3.2: dependencies: big-integer: 1.6.52 @@ -4462,6 +9450,22 @@ snapshots: dependencies: balanced-match: 4.0.4 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.6: + dependencies: + baseline-browser-mapping: 2.10.43 + caniuse-lite: 1.0.30001806 + electron-to-chromium: 1.5.393 + node-releases: 2.0.51 + update-browserslist-db: 1.2.3(browserslist@4.28.6) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + buffer-crc32@0.2.13: {} buffer-from@1.1.2: {} @@ -4492,6 +9496,8 @@ snapshots: transitivePeerDependencies: - supports-color + bytes@3.1.2: {} + bytestreamjs@2.0.1: {} cacheable-lookup@5.0.4: {} @@ -4511,10 +9517,34 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001806: {} + ccount@2.0.1: {} chai@6.2.2: {} + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -4530,8 +9560,31 @@ snapshots: chownr@3.0.0: {} + chrome-launcher@0.15.2: + dependencies: + '@types/node': 26.1.1 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + + chromium-edge-launcher@0.3.0: + dependencies: + '@types/node': 26.1.1 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + mkdirp: 1.0.4 + transitivePeerDependencies: + - supports-color + chromium-pickle-js@0.2.0: {} + ci-info@2.0.0: {} + + ci-info@3.9.0: {} + ci-info@4.3.1: {} ci-info@4.4.0: {} @@ -4540,6 +9593,14 @@ snapshots: dependencies: clsx: 2.1.1 + cli-cursor@2.1.0: + dependencies: + restore-cursor: 2.0.0 + + cli-spinners@2.9.2: {} + + client-only@0.0.1: {} + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -4550,14 +9611,32 @@ snapshots: dependencies: mimic-response: 1.0.1 + clone@1.0.4: {} + clsx@2.1.1: {} + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 @@ -4566,13 +9645,33 @@ snapshots: commander@12.1.0: {} + commander@2.20.3: {} + commander@5.1.0: {} + commander@7.2.0: {} + commander@9.5.0: optional: true compare-version@0.1.2: {} + compressible@2.0.18: + dependencies: + mime-db: 1.52.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + concat-map@0.0.1: {} conf@10.2.0: @@ -4588,27 +9687,78 @@ snapshots: pkg-up: 3.1.0 semver: 7.8.5 + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + convert-source-map@2.0.0: {} cookie-es@3.1.1: {} + core-js-compat@3.49.0: + dependencies: + browserslist: 4.28.6 + core-util-is@1.0.3: {} cross-dirname@0.1.0: optional: true - cross-spawn@7.0.6: + cross-fetch@3.2.0: + dependencies: + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-in-js-utils@3.1.0: + dependencies: + hyphenate-style-name: 1.1.0 + + css.escape@1.5.1: {} + + csstype@3.2.3: {} + + data-view-buffer@1.0.2: dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 - csstype@3.2.3: {} + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 debounce-fn@4.0.0: dependencies: mimic-fn: 3.1.0 + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + debug@4.4.3: dependencies: ms: 2.1.3 @@ -4617,10 +9767,20 @@ snapshots: dependencies: character-entities: 2.0.2 + decode-uri-component@0.2.2: {} + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + defer-to-connect@2.0.1: {} define-data-property@1.1.4: @@ -4628,7 +9788,6 @@ snapshots: es-define-property: 1.0.1 es-errors: 1.3.0 gopd: 1.2.0 - optional: true define-lazy-prop@2.0.0: {} @@ -4637,14 +9796,19 @@ snapshots: define-data-property: 1.1.4 has-property-descriptors: 1.0.2 object-keys: 1.1.1 - optional: true delayed-stream@1.0.0: {} + depd@2.0.0: {} + dequal@2.0.3: {} + destroy@1.2.0: {} + detect-libc@2.1.2: {} + detect-node-es@1.1.0: {} + detect-node@2.1.0: optional: true @@ -4667,8 +9831,16 @@ snapshots: - electron-builder-squirrel-windows - supports-color + dnssd-advertise@1.1.6: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + dom-accessibility-api@0.5.16: {} + dom-accessibility-api@0.6.3: {} + dot-prop@6.0.1: dependencies: is-obj: 2.0.0 @@ -4689,6 +9861,8 @@ snapshots: dependencies: readable-stream: 2.3.8 + ee-first@1.1.1: {} + ejs@3.1.10: dependencies: jake: 10.9.4 @@ -4737,6 +9911,8 @@ snapshots: conf: 10.2.0 type-fest: 2.19.0 + electron-to-chromium@1.5.393: {} + electron-updater@6.8.9: dependencies: builder-util-runtime: 9.7.0 @@ -4776,6 +9952,10 @@ snapshots: emoji-regex@8.0.0: {} + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -4791,10 +9971,97 @@ snapshots: err-code@2.0.3: {} + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-abstract-get@1.0.0: + dependencies: + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + is-callable: 1.2.7 + object-inspect: 1.13.4 + + es-abstract@1.24.2: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.4 + function.prototype.name: 1.2.0 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.4 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.11 + string.prototype.trimend: 1.0.10 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.8 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.22 + es-define-property@1.0.1: {} es-errors@1.3.0: {} + es-iterator-helpers@1.4.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + math-intrinsics: 1.1.0 + es-module-lexer@2.3.0: {} es-object-atoms@1.1.2: @@ -4808,24 +10075,475 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.4 + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.4 + + es-to-primitive@1.3.4: + dependencies: + es-abstract-get: 1.0.0 + es-define-property: 1.0.1 + es-errors: 1.3.0 + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + es6-error@4.1.1: optional: true escalade@3.2.0: {} - escape-string-regexp@4.0.0: - optional: true + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@4.0.0: {} escape-string-regexp@5.0.0: {} + eslint-config-expo@57.0.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.64.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + eslint: 9.39.5(jiti@2.7.0) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-expo: 1.1.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-react: 7.37.5(eslint@9.39.5(jiti@2.7.0)) + eslint-plugin-react-hooks: 7.1.1(eslint@9.39.5(jiti@2.7.0)) + globals: 16.5.0 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + - typescript + + eslint-import-resolver-node@0.3.10: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.2 + resolve: 2.0.0-next.7 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.5(jiti@2.7.0) + get-tsconfig: 4.14.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.17 + unrs-resolver: 1.12.2 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + eslint: 9.39.5(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.5(jiti@2.7.0)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-expo@1.1.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3): + dependencies: + '@typescript-eslint/types': 8.64.0 + '@typescript-eslint/utils': 8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + eslint: 9.39.5(jiti@2.7.0) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.5(jiti@2.7.0) + eslint-import-resolver-node: 0.3.10 + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.5(jiti@2.7.0)) + hasown: 2.0.4 + is-core-module: 2.16.2 + is-glob: 4.0.3 + minimatch: 3.1.5 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.10 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.64.0(eslint@9.39.5(jiti@2.7.0))(typescript@6.0.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-react-hooks@7.1.1(eslint@9.39.5(jiti@2.7.0)): + dependencies: + '@babel/core': 7.29.7 + '@babel/parser': 7.29.7 + eslint: 9.39.5(jiti@2.7.0) + hermes-parser: 0.25.1 + zod: 3.25.76 + zod-validation-error: 4.0.2(zod@3.25.76) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react@7.37.5(eslint@9.39.5(jiti@2.7.0)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.4.0 + eslint: 9.39.5(jiti@2.7.0) + estraverse: 5.3.0 + hasown: 2.0.4 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.7 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.5(jiti@2.7.0): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.5(jiti@2.7.0)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.6 + '@eslint/js': 9.39.5 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.7.0 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.17.0 + acorn-jsx: 5.3.2(acorn@8.17.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + estree-util-is-identifier-name@3.0.0: {} estree-walker@3.0.3: dependencies: '@types/estree': 1.0.9 + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + expect-type@1.4.0: {} + expo-application@57.0.2(expo@57.0.7): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + + expo-asset@57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3): + dependencies: + '@expo/image-utils': 0.11.3(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-constants: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + transitivePeerDependencies: + - supports-color + - typescript + + expo-constants@57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)): + dependencies: + '@expo/env': 2.4.2 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + transitivePeerDependencies: + - supports-color + + expo-file-system@57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + expo-font@57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + fontfaceobserver: 2.3.0 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + expo-glass-effect@57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + expo-keep-awake@57.0.1(expo@57.0.7)(react@19.2.3): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + + expo-linking@57.0.3(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + expo-constants: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + invariant: 2.2.4 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + transitivePeerDependencies: + - expo + - supports-color + + expo-modules-autolinking@57.0.8(typescript@6.0.3): + dependencies: + '@expo/require-utils': 57.0.3(typescript@6.0.3) + '@expo/spawn-async': 1.8.0 + chalk: 4.1.2 + commander: 7.2.0 + transitivePeerDependencies: + - supports-color + - typescript + + expo-modules-core@57.0.6(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + '@expo/expo-modules-macros-plugin': 0.6.1 + expo-modules-jsi: 57.0.3(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + invariant: 2.2.4 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + optionalDependencies: + react-native-worklets: 0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + + expo-modules-jsi@57.0.3(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)): + dependencies: + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + expo-notifications@57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3): + dependencies: + '@expo/image-utils': 0.11.3(typescript@6.0.3) + abort-controller: 3.0.0 + badgin: 1.2.3 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-application: 57.0.2(expo@57.0.7) + expo-constants: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + transitivePeerDependencies: + - supports-color + - typescript + + expo-router@57.0.7(8d7c44b937c34bc9b52672f8f7e374b1): + dependencies: + '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/metro-runtime': 57.0.6(@expo/log-box@57.0.1)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/schema-utils': 57.0.2 + '@expo/ui': 57.0.7(@babel/core@7.29.7)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@radix-ui/react-slot': 1.3.0(@types/react@19.2.17)(react@19.2.3) + '@radix-ui/react-tabs': 1.1.17(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@react-native-masked-view/masked-view': 0.3.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@testing-library/jest-dom': 6.9.1 + '@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1) + client-only: 0.0.1 + color: 4.2.3 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-constants: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + expo-glass-effect: 57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + expo-linking: 57.0.3(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + expo-server: 57.0.1 + expo-symbols: 57.0.1(expo-font@57.0.1)(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + fast-deep-equal: 3.1.3 + invariant: 2.2.4 + nanoid: 3.3.15 + query-string: 7.1.3 + react: 19.2.3 + react-fast-compare: 3.2.2 + react-is: 19.2.7 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + react-native-drawer-layout: 4.2.8(react-native-gesture-handler@2.32.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-reanimated@4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-native-safe-area-context: 5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-native-screens: 4.25.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + server-only: 0.0.1 + sf-symbols-typescript: 2.2.0 + shallowequal: 1.1.0 + standard-navigation: 0.0.5 + vaul: 1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + optionalDependencies: + react-dom: 19.2.3(react@19.2.3) + react-native-gesture-handler: 2.32.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-native-web: 0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + transitivePeerDependencies: + - '@babel/core' + - '@testing-library/dom' + - '@types/react' + - '@types/react-dom' + - expo-font + - react-native-worklets + - supports-color + + expo-secure-store@57.0.1(expo@57.0.7): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + + expo-server@57.0.1: {} + + expo-speech-recognition@56.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + expo-speech@57.0.1(expo@57.0.7): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + + expo-splash-screen@57.0.4(expo@57.0.7)(typescript@6.0.3): + dependencies: + '@expo/config-plugins': 57.0.5(typescript@6.0.3) + '@expo/image-utils': 0.11.3(typescript@6.0.3) + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + xml2js: 0.6.0 + transitivePeerDependencies: + - supports-color + - typescript + + expo-status-bar@57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + expo-symbols@57.0.1(expo-font@57.0.1)(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + '@expo-google-fonts/material-symbols': 0.4.41 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-font: 57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + sf-symbols-typescript: 2.2.0 + + expo-system-ui@57.0.1(expo@57.0.7)(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)): + dependencies: + '@react-native/normalize-colors': 0.86.0 + debug: 4.4.3 + expo: 57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + optionalDependencies: + react-native-web: 0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + transitivePeerDependencies: + - supports-color + + expo@57.0.7(@babel/core@7.29.7)(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-router@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3): + dependencies: + '@babel/runtime': 7.29.7 + '@expo/cli': 57.0.9(@expo/dom-webview@57.0.1)(@expo/metro-runtime@57.0.6)(expo-constants@57.0.6)(expo-font@57.0.1)(expo-router@57.0.7)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + '@expo/config': 57.0.5(typescript@6.0.3) + '@expo/config-plugins': 57.0.5(typescript@6.0.3) + '@expo/devtools': 57.0.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/fingerprint': 0.20.5 + '@expo/local-build-cache-provider': 57.0.4(typescript@6.0.3) + '@expo/log-box': 57.0.1(@expo/dom-webview@57.0.1)(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/metro': 56.0.0 + '@expo/metro-config': 57.0.6(expo@57.0.7)(typescript@6.0.3) + '@ungap/structured-clone': 1.3.2 + babel-preset-expo: 57.0.3(@babel/core@7.29.7)(@babel/runtime@7.29.7)(expo@57.0.7)(react-refresh@0.14.2) + expo-asset: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3)(typescript@6.0.3) + expo-constants: 57.0.6(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + expo-file-system: 57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3)) + expo-font: 57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + expo-keep-awake: 57.0.1(expo@57.0.7)(react@19.2.3) + expo-modules-autolinking: 57.0.8(typescript@6.0.3) + expo-modules-core: 57.0.6(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + pretty-format: 29.7.0 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + react-refresh: 0.14.2 + whatwg-url-minimum: 0.1.2 + optionalDependencies: + '@expo/dom-webview': 57.0.1(expo@57.0.7)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + '@expo/metro-runtime': 57.0.6(@expo/log-box@57.0.1)(expo@57.0.7)(react-dom@19.2.3(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-dom: 19.2.3(react@19.2.3) + react-native-web: 0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + transitivePeerDependencies: + - '@babel/core' + - bufferutil + - expo-router + - expo-widgets + - react-native-worklets + - react-server-dom-webpack + - supports-color + - typescript + - utf-8-validate + exponential-backoff@3.1.3: {} extend@3.0.2: {} @@ -4842,8 +10560,32 @@ snapshots: fast-deep-equal@3.1.3: {} + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + fast-uri@3.1.3: {} + fb-dotslash@0.5.8: {} + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fbjs-css-vars@1.0.2: {} + + fbjs@3.0.5: + dependencies: + cross-fetch: 3.2.0 + fbjs-css-vars: 1.0.2 + loose-envify: 1.4.0 + object-assign: 4.1.1 + promise: 7.3.1 + setimmediate: 1.0.5 + ua-parser-js: 1.0.41 + transitivePeerDependencies: + - encoding + fd-slicer@1.1.0: dependencies: pend: 1.2.0 @@ -4852,13 +10594,57 @@ snapshots: optionalDependencies: picomatch: 4.0.5 + fetch-nodeshim@0.4.10: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + filelist@1.0.6: dependencies: minimatch: 5.1.9 + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + filter-obj@1.1.0: {} + + finalhandler@1.1.2: + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.3.0 + parseurl: 1.3.3 + statuses: 1.5.0 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + find-up@3.0.0: dependencies: - locate-path: 3.0.0 + locate-path: 3.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + flow-enums-runtime@0.0.6: {} + + fontfaceobserver@2.3.0: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 form-data@4.0.6: dependencies: @@ -4868,6 +10654,8 @@ snapshots: hasown: 2.0.4 mime-types: 2.1.35 + fresh@0.5.2: {} + fs-extra@10.1.0: dependencies: graceful-fs: 4.2.11 @@ -4915,6 +10703,24 @@ snapshots: function-bind@1.1.2: {} + function.prototype.name@1.2.0: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + es-define-property: 1.0.1 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + hasown: 2.0.4 + is-callable: 1.2.7 + is-document.all: 1.0.0 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} get-intrinsic@1.3.0: @@ -4930,6 +10736,8 @@ snapshots: hasown: 2.0.4 math-intrinsics: 1.1.0 + get-nonce@1.0.1: {} + get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 @@ -4939,6 +10747,22 @@ snapshots: dependencies: pump: 3.0.4 + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + getenv@2.0.0: {} + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@13.0.6: dependencies: minimatch: 10.2.5 @@ -4964,11 +10788,14 @@ snapshots: serialize-error: 7.0.1 optional: true + globals@14.0.0: {} + + globals@16.5.0: {} + globalthis@1.0.4: dependencies: define-properties: 1.2.1 gopd: 1.2.0 - optional: true gopd@1.2.0: {} @@ -4988,12 +10815,19 @@ snapshots: graceful-fs@4.2.11: {} + has-bigints@1.1.0: {} + + has-flag@3.0.0: {} + has-flag@4.0.0: {} has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 - optional: true + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 has-symbols@1.1.0: {} @@ -5084,16 +10918,58 @@ snapshots: property-information: 7.2.0 space-separated-tokens: 2.0.2 + hermes-compiler@250829098.0.14: {} + + hermes-estree@0.25.1: {} + + hermes-estree@0.35.0: {} + + hermes-estree@0.36.0: {} + + hermes-estree@0.36.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + hermes-parser@0.35.0: + dependencies: + hermes-estree: 0.35.0 + + hermes-parser@0.36.0: + dependencies: + hermes-estree: 0.36.0 + + hermes-parser@0.36.1: + dependencies: + hermes-estree: 0.36.1 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + hosted-git-info@4.1.0: dependencies: lru-cache: 6.0.0 + hosted-git-info@7.0.2: + dependencies: + lru-cache: 10.4.3 + html-url-attributes@3.0.1: {} html-void-elements@3.0.0: {} http-cache-semantics@4.2.0: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.4 @@ -5113,6 +10989,25 @@ snapshots: transitivePeerDependencies: - supports-color + hyphenate-style-name@1.1.0: {} + + ignore@5.3.2: {} + + ignore@7.0.6: {} + + image-size@1.2.1: + dependencies: + queue: 6.0.2 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -5124,6 +11019,20 @@ snapshots: inline-style-parser@0.2.7: {} + inline-style-prefixer@7.0.1: + dependencies: + css-in-js-utils: 3.1.0 + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.4 + side-channel: 1.1.1 + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + is-alphabetical@2.0.1: {} is-alphanumerical@2.0.1: @@ -5131,24 +11040,144 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.3.4: {} + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.8.5 + + is-callable@1.2.7: {} + + is-core-module@2.16.2: + dependencies: + hasown: 2.0.4 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-decimal@2.0.1: {} is-docker@2.2.1: {} + is-document.all@1.0.0: + dependencies: + call-bound: 1.0.4 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + is-hexadecimal@2.0.1: {} + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + is-obj@2.0.0: {} is-plain-obj@4.1.0: {} + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.22 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-wsl@2.2.0: dependencies: is-docker: 2.2.1 isarray@1.0.0: {} + isarray@2.0.5: {} + isbinaryfile@4.0.10: {} isbinaryfile@5.0.7: {} @@ -5163,12 +11192,50 @@ snapshots: isomorphic.js@0.2.5: {} + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + jake@10.9.4: dependencies: async: 3.2.6 filelist: 1.0.6 picocolors: 1.1.1 + jest-get-type@29.6.3: {} + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 26.1.1 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.2 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-worker@29.7.0: + dependencies: + '@types/node': 26.1.1 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jimp-compact@0.16.1: {} + jiti@2.7.0: {} js-tokens@4.0.0: {} @@ -5177,15 +11244,27 @@ snapshots: dependencies: argparse: 2.0.1 + jsc-safe-url@0.2.4: {} + + jsesc@3.1.0: {} + json-buffer@3.0.1: {} + json-schema-traverse@0.4.1: {} + json-schema-traverse@1.0.0: {} json-schema-typed@7.0.3: {} + json-stable-stringify-without-jsonify@1.0.1: {} + json-stringify-safe@5.0.1: optional: true + json5@1.0.2: + dependencies: + minimist: 1.2.8 + json5@2.2.3: {} jsonfile@4.0.0: @@ -5198,6 +11277,13 @@ snapshots: optionalDependencies: graceful-fs: 4.2.11 + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -5206,14 +11292,30 @@ snapshots: kleur@4.1.5: {} + lan-network@0.2.1: {} + lazy-val@1.0.5: {} + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + lexical@0.41.0: {} lib0@0.2.117: dependencies: isomorphic.js: 0.2.5 + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + lightningcss-android-arm64@1.32.0: optional: true @@ -5268,18 +11370,42 @@ snapshots: p-locate: 3.0.0 path-exists: 3.0.0 + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + lodash.escaperegexp@4.1.2: {} lodash.isequal@4.5.0: {} + lodash.merge@4.6.2: {} + + lodash.throttle@4.1.1: {} + lodash@4.18.1: {} + log-symbols@2.2.0: + dependencies: + chalk: 2.4.2 + longest-streak@3.1.0: {} + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lowercase-keys@2.0.0: {} + lru-cache@10.4.3: {} + lru-cache@11.5.2: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lru-cache@6.0.0: dependencies: yallist: 4.0.0 @@ -5294,8 +11420,14 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + markdown-table@3.0.4: {} + marky@1.3.0: {} + matcher@3.0.0: dependencies: escape-string-regexp: 4.0.0 @@ -5428,38 +11560,218 @@ snapshots: '@types/mdast': 4.0.4 mdast-util-find-and-replace: 3.0.2 - mdast-util-phrasing@4.1.0: + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + + mdast-util-to-hast@13.2.1: + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.2 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + + memoize-one@5.2.1: {} + + memoize-one@6.0.0: {} + + merge-stream@2.0.0: {} + + metro-babel-transformer@0.84.4: + dependencies: + '@babel/core': 7.29.7 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.35.0 + metro-cache-key: 0.84.4 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-cache-key@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache@0.84.4: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.84.4 + transitivePeerDependencies: + - supports-color + + metro-config@0.84.4: + dependencies: + connect: 3.7.0 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.84.4 + metro-cache: 0.84.4 + metro-core: 0.84.4 + metro-runtime: 0.84.4 + yaml: 2.9.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-core@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.84.4 + + metro-file-map@0.84.4: + dependencies: + debug: 4.4.3 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + metro-minify-terser@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.49.0 + + metro-resolver@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-runtime@0.84.4: + dependencies: + '@babel/runtime': 7.29.7 + flow-enums-runtime: 0.0.6 + + metro-source-map@0.84.4: + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.84.4 + nullthrows: 1.1.1 + ob1: 0.84.4 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.84.4: dependencies: - '@types/mdast': 4.0.4 - unist-util-is: 6.0.1 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.84.4 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color - mdast-util-to-hast@13.2.1: + metro-transform-plugins@0.84.4: dependencies: - '@types/hast': 3.0.5 - '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.3.2 - devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.1 - trim-lines: 3.0.1 - unist-util-position: 5.0.0 - unist-util-visit: 5.1.0 - vfile: 6.0.3 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color - mdast-util-to-markdown@2.1.2: - dependencies: - '@types/mdast': 4.0.4 - '@types/unist': 3.0.3 - longest-streak: 3.1.0 - mdast-util-phrasing: 4.1.0 - mdast-util-to-string: 4.0.0 - micromark-util-classify-character: 2.0.1 - micromark-util-decode-string: 2.0.1 - unist-util-visit: 5.1.0 - zwitch: 2.0.4 + metro-transform-worker@0.84.4: + dependencies: + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + flow-enums-runtime: 0.0.6 + metro: 0.84.4 + metro-babel-transformer: 0.84.4 + metro-cache: 0.84.4 + metro-cache-key: 0.84.4 + metro-minify-terser: 0.84.4 + metro-source-map: 0.84.4 + metro-transform-plugins: 0.84.4 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate - mdast-util-to-string@4.0.0: + metro@0.84.4: dependencies: - '@types/mdast': 4.0.4 + '@babel/code-frame': 7.29.7 + '@babel/core': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + accepts: 2.0.0 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.35.0 + image-size: 1.2.1 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.84.4 + metro-cache: 0.84.4 + metro-cache-key: 0.84.4 + metro-config: 0.84.4 + metro-core: 0.84.4 + metro-file-map: 0.84.4 + metro-resolver: 0.84.4 + metro-runtime: 0.84.4 + metro-source-map: 0.84.4 + metro-symbolicate: 0.84.4 + metro-transform-plugins: 0.84.4 + metro-transform-worker: 0.84.4 + mime-types: 3.0.2 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.13 + yargs: 17.7.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate micromark-core-commonmark@2.0.3: dependencies: @@ -5652,14 +11964,29 @@ snapshots: transitivePeerDependencies: - supports-color + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.2 + mime-db@1.52.0: {} + mime-db@1.54.0: {} + mime-types@2.1.35: dependencies: mime-db: 1.52.0 + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: {} + mime@2.6.0: {} + mimic-fn@1.2.0: {} + mimic-fn@2.1.0: {} mimic-fn@3.1.0: {} @@ -5668,6 +11995,8 @@ snapshots: mimic-response@3.1.0: {} + min-indent@1.0.1: {} + minimatch@10.2.5: dependencies: brace-expansion: 5.0.7 @@ -5696,12 +12025,20 @@ snapshots: dependencies: minimist: 1.2.8 + mkdirp@1.0.4: {} + mrmime@2.0.1: {} + ms@2.0.0: {} + ms@2.1.3: {} + multitars@1.0.0: {} + nanoid@3.3.15: {} + napi-postinstall@0.3.4: {} + native-run@2.0.3: dependencies: '@ionic/utils-fs': 3.1.7 @@ -5718,6 +12055,14 @@ snapshots: transitivePeerDependencies: - supports-color + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + negotiator@1.0.0: {} + node-abi@4.33.0: dependencies: semver: 7.8.5 @@ -5726,6 +12071,19 @@ snapshots: dependencies: semver: 7.8.5 + node-exports-info@1.6.2: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + + node-forge@1.4.0: {} + node-gyp@12.4.0: dependencies: env-paths: 2.2.1 @@ -5741,32 +12099,129 @@ snapshots: node-int64@0.4.0: {} + node-releases@2.0.51: {} + nopt@9.0.0: dependencies: abbrev: 4.0.0 normalize-url@6.1.0: {} - object-keys@1.1.1: - optional: true + npm-package-arg@11.0.3: + dependencies: + hosted-git-info: 7.0.2 + proc-log: 4.2.0 + semver: 7.8.5 + validate-npm-package-name: 5.0.1 + + nullthrows@1.1.1: {} + + ob1@0.84.4: + dependencies: + flow-enums-runtime: 0.0.6 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 obug@2.1.3: {} + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + once@1.4.0: dependencies: wrappy: 1.0.2 + onetime@2.0.1: + dependencies: + mimic-fn: 1.2.0 + onetime@5.1.2: dependencies: mimic-fn: 2.1.0 + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + open@8.4.2: dependencies: define-lazy-prop: 2.0.0 is-docker: 2.2.1 is-wsl: 2.2.0 - oxfmt@0.57.0(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)): + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@3.4.0: + dependencies: + chalk: 2.4.2 + cli-cursor: 2.1.0 + cli-spinners: 2.9.2 + log-symbols: 2.2.0 + strip-ansi: 5.2.0 + wcwidth: 1.0.1 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + oxfmt@0.57.0(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)): dependencies: tinypool: 2.1.0 optionalDependencies: @@ -5789,9 +12244,9 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.57.0 '@oxfmt/binding-win32-ia32-msvc': 0.57.0 '@oxfmt/binding-win32-x64-msvc': 0.57.0 - vite-plus: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + vite-plus: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) - oxfmt@0.57.0(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)): + oxfmt@0.57.0(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)): dependencies: tinypool: 2.1.0 optionalDependencies: @@ -5814,7 +12269,7 @@ snapshots: '@oxfmt/binding-win32-arm64-msvc': 0.57.0 '@oxfmt/binding-win32-ia32-msvc': 0.57.0 '@oxfmt/binding-win32-x64-msvc': 0.57.0 - vite-plus: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) + vite-plus: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) oxlint-tsgolint@0.24.0: optionalDependencies: @@ -5825,7 +12280,7 @@ snapshots: '@oxlint-tsgolint/win32-arm64': 0.24.0 '@oxlint-tsgolint/win32-x64': 0.24.0 - oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)): + oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.72.0 '@oxlint/binding-android-arm64': 1.72.0 @@ -5847,9 +12302,9 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.72.0 '@oxlint/binding-win32-x64-msvc': 1.72.0 oxlint-tsgolint: 0.24.0 - vite-plus: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) + vite-plus: 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) - oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)): + oxlint@1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.72.0 '@oxlint/binding-android-arm64': 1.72.0 @@ -5871,7 +12326,7 @@ snapshots: '@oxlint/binding-win32-ia32-msvc': 1.72.0 '@oxlint/binding-win32-x64-msvc': 1.72.0 oxlint-tsgolint: 0.24.0 - vite-plus: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) + vite-plus: 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) p-cancelable@2.1.1: {} @@ -5887,10 +12342,18 @@ snapshots: dependencies: p-limit: 2.3.0 + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + p-try@2.2.0: {} package-json-from-dist@1.0.1: {} + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + parse-entities@4.0.2: dependencies: '@types/unist': 2.0.11 @@ -5901,16 +12364,26 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-png@2.1.0: + dependencies: + pngjs: 3.4.0 + parse5@7.3.0: dependencies: entities: 6.0.1 + parseurl@1.3.3: {} + path-exists@3.0.0: {} + path-exists@4.0.0: {} + path-is-absolute@1.0.1: {} path-key@3.1.1: {} + path-parse@1.0.7: {} + path-scurry@2.0.2: dependencies: lru-cache: 11.5.2 @@ -5924,6 +12397,8 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.2: {} + picomatch@4.0.5: {} pkg-up@3.1.0: @@ -5953,8 +12428,14 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + pngjs@3.4.0: {} + pngjs@7.0.0: {} + possible-typed-array-names@1.1.0: {} + + postcss-value-parser@4.2.0: {} + postcss@8.5.16: dependencies: nanoid: 3.3.15 @@ -5966,14 +12447,24 @@ snapshots: commander: 9.5.0 optional: true + prelude-ls@1.2.1: {} + pretty-format@27.5.1: dependencies: ansi-regex: 5.0.1 ansi-styles: 5.2.0 react-is: 17.0.2 + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + prismjs@1.30.0: {} + proc-log@4.2.0: {} + proc-log@6.1.0: {} process-nextick-args@2.0.1: {} @@ -5985,11 +12476,25 @@ snapshots: err-code: 2.0.3 retry: 0.12.0 + promise@7.3.1: + dependencies: + asap: 2.0.6 + + promise@8.3.0: + dependencies: + asap: 2.0.6 + prompts@2.4.2: dependencies: kleur: 3.0.3 sisteransi: 1.0.5 + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + proper-lockfile@4.1.2: dependencies: graceful-fs: 4.2.11 @@ -6003,6 +12508,8 @@ snapshots: end-of-stream: 1.4.5 once: 1.4.0 + punycode@2.3.1: {} + pvtsutils@1.3.6: dependencies: tslib: 2.8.1 @@ -6013,8 +12520,34 @@ snapshots: dependencies: react: 19.2.6 + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + + queue@6.0.2: + dependencies: + inherits: 2.0.4 + quick-lru@5.1.1: {} + range-parser@1.2.1: {} + + react-devtools-core@6.1.5: + dependencies: + shell-quote: 1.10.0 + ws: 7.5.13 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + react-dom@19.2.3(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + react-dom@19.2.6(react@19.2.6): dependencies: react: 19.2.6 @@ -6024,8 +12557,20 @@ snapshots: dependencies: react: 19.2.6 + react-fast-compare@3.2.2: {} + + react-freeze@1.0.4(react@19.2.3): + dependencies: + react: 19.2.3 + + react-is@16.13.1: {} + react-is@17.0.2: {} + react-is@18.3.1: {} + + react-is@19.2.7: {} + react-markdown@10.1.0(@types/react@19.2.17)(react@19.2.6): dependencies: '@types/hast': 3.0.5 @@ -6044,29 +12589,232 @@ snapshots: transitivePeerDependencies: - supports-color + react-native-drawer-layout@4.2.8(react-native-gesture-handler@2.32.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native-reanimated@4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + color: 4.2.3 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + react-native-gesture-handler: 2.32.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-native-reanimated: 4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + use-latest-callback: 0.2.6(react@19.2.3) + + react-native-gesture-handler@2.32.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + '@egjs/hammerjs': 2.0.17 + '@types/react-test-renderer': 19.1.0 + hoist-non-react-statics: 3.3.2 + invariant: 2.2.4 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + react-native-is-edge-to-edge@1.3.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + react-native-reanimated@4.5.0(react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + react-native-is-edge-to-edge: 1.3.1(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + react-native-worklets: 0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + semver: 7.8.5 + + react-native-safe-area-context@5.7.0(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + + react-native-screens@4.25.2(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + react: 19.2.3 + react-freeze: 1.0.4(react@19.2.3) + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + warn-once: 0.1.1 + + react-native-web@0.21.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@babel/runtime': 7.29.7 + '@react-native/normalize-colors': 0.74.89 + fbjs: 3.0.5 + inline-style-prefixer: 7.0.1 + memoize-one: 6.0.0 + nullthrows: 1.1.1 + postcss-value-parser: 4.2.0 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styleq: 0.1.3 + transitivePeerDependencies: + - encoding + + react-native-worklets@0.10.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3): + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-arrow-functions': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-class-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-classes': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-nullish-coalescing-operator': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-optional-chaining': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-shorthand-properties': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-template-literals': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-unicode-regex': 7.29.7(@babel/core@7.29.7) + '@babel/preset-typescript': 7.29.7(@babel/core@7.29.7) + '@babel/types': 7.29.7 + '@react-native/metro-config': 0.86.0(@babel/core@7.29.7) + convert-source-map: 2.0.0 + react: 19.2.3 + react-native: 0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3) + semver: 7.8.5 + transitivePeerDependencies: + - supports-color + + react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3): + dependencies: + '@react-native/assets-registry': 0.86.0 + '@react-native/codegen': 0.86.0(@babel/core@7.29.7) + '@react-native/community-cli-plugin': 0.86.0(@react-native/metro-config@0.86.0(@babel/core@7.29.7)) + '@react-native/gradle-plugin': 0.86.0 + '@react-native/js-polyfills': 0.86.0 + '@react-native/normalize-colors': 0.86.0 + '@react-native/virtualized-lists': 0.86.0(@types/react@19.2.17)(react-native@0.86.0(@babel/core@7.29.7)(@react-native/metro-config@0.86.0(@babel/core@7.29.7))(@types/react@19.2.17)(react@19.2.3))(react@19.2.3) + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + babel-plugin-syntax-hermes-parser: 0.36.0 + base64-js: 1.5.1 + commander: 12.1.0 + flow-enums-runtime: 0.0.6 + hermes-compiler: 250829098.0.14 + invariant: 2.2.4 + memoize-one: 5.2.1 + metro-runtime: 0.84.4 + metro-source-map: 0.84.4 + nullthrows: 1.1.1 + pretty-format: 29.7.0 + promise: 8.3.0 + react: 19.2.3 + react-devtools-core: 6.1.5 + react-refresh: 0.14.2 + regenerator-runtime: 0.13.11 + scheduler: 0.27.0 + semver: 7.8.5 + stacktrace-parser: 0.1.11 + tinyglobby: 0.2.17 + whatwg-fetch: 3.6.20 + ws: 7.5.13 + yargs: 17.7.3 + optionalDependencies: + '@types/react': 19.2.17 + transitivePeerDependencies: + - '@babel/core' + - '@react-native-community/cli' + - '@react-native/metro-config' + - bufferutil + - supports-color + - utf-8-validate + + react-refresh@0.14.2: {} + + react-remove-scroll-bar@2.3.8(@types/react@19.2.17)(react@19.2.3): + dependencies: + react: 19.2.3 + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.3) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + react-remove-scroll@2.7.2(@types/react@19.2.17)(react@19.2.3): + dependencies: + react: 19.2.3 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.17)(react@19.2.3) + react-style-singleton: 2.2.3(@types/react@19.2.17)(react@19.2.3) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@19.2.17)(react@19.2.3) + use-sidecar: 1.1.3(@types/react@19.2.17)(react@19.2.3) + optionalDependencies: + '@types/react': 19.2.17 + + react-style-singleton@2.2.3(@types/react@19.2.17)(react@19.2.3): + dependencies: + get-nonce: 1.0.1 + react: 19.2.3 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + react@19.2.3: {} + react@19.2.6: {} - read-binary-file-arch@1.0.6: + read-binary-file-arch@1.0.6: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.13.11: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + regexpu-core@6.4.0: dependencies: - debug: 4.4.3 - transitivePeerDependencies: - - supports-color + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.2 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 + regjsgen@0.8.0: {} - readable-stream@3.6.2: + regjsparser@0.13.2: dependencies: - inherits: 2.0.4 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 + jsesc: 3.1.0 rehype-raw@7.0.0: dependencies: @@ -6131,10 +12879,39 @@ snapshots: resolve-alpn@1.2.1: {} + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve-workspace-root@2.0.1: {} + + resolve@1.22.12: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.7: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.2 + node-exports-info: 1.6.2 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + responselike@2.0.1: dependencies: lowercase-keys: 2.0.0 + restore-cursor@2.0.0: + dependencies: + onetime: 2.0.1 + signal-exit: 3.0.7 + retry@0.12.0: {} rimraf@2.6.3: @@ -6156,8 +12933,29 @@ snapshots: sprintf-js: 1.1.3 optional: true + safe-array-concat@1.1.4: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + sanitize-filename@1.6.4: dependencies: truncate-utf8-bytes: 1.0.2 @@ -6179,6 +12977,26 @@ snapshots: semver@7.8.5: {} + 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 + transitivePeerDependencies: + - supports-color + + serialize-error@2.1.0: {} + serialize-error@7.0.1: dependencies: type-fest: 0.13.1 @@ -6190,16 +13008,97 @@ snapshots: seroval@1.5.5: {} + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + server-only@0.0.1: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + + setimmediate@1.0.5: {} + + setprototypeof@1.2.0: {} + + sf-symbols-typescript@2.2.0: {} + + shallowequal@1.1.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 shebang-regex@3.0.0: {} + shell-quote@1.10.0: {} + + side-channel-list@1.0.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.1: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.1 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + siginfo@2.0.0: {} signal-exit@3.0.7: {} + simple-plist@1.3.1: + dependencies: + bplist-creator: 0.1.0 + bplist-parser: 0.3.1 + plist: 3.1.0 + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + simple-update-notifier@2.0.0: dependencies: semver: 7.8.5 @@ -6218,6 +13117,8 @@ snapshots: astral-regex: 2.0.0 is-fullwidth-code-point: 3.0.0 + slugify@1.6.9: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: @@ -6225,27 +13126,99 @@ snapshots: buffer-from: 1.1.2 source-map: 0.6.1 + source-map@0.5.7: {} + source-map@0.6.1: {} space-separated-tokens@2.0.2: {} + split-on-first@1.1.0: {} + split2@4.2.0: {} sprintf-js@1.1.3: optional: true + stable-hash@0.0.5: {} + stackback@0.0.2: {} + stackframe@1.3.4: {} + + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + + standard-navigation@0.0.5: {} + stat-mode@1.0.0: {} + statuses@1.5.0: {} + + statuses@2.0.2: {} + std-env@4.2.0: {} + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + stream-buffers@2.2.0: {} + + strict-uri-encode@2.0.0: {} + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.1 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.2 + + string.prototype.trim@1.2.11: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.2 + es-object-atoms: 1.1.2 + has-property-descriptors: 1.0.2 + safe-regex-test: 1.1.0 + + string.prototype.trimend@1.0.10: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + es-object-atoms: 1.1.2 + string_decoder@1.1.1: dependencies: safe-buffer: 5.1.2 @@ -6255,10 +13228,24 @@ snapshots: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 + strip-ansi@5.2.0: + dependencies: + ansi-regex: 4.1.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 + strip-bom@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + structured-headers@0.4.1: {} + style-to-js@1.1.21: dependencies: style-to-object: 1.0.14 @@ -6267,16 +13254,33 @@ snapshots: dependencies: inline-style-parser: 0.2.7 + styleq@0.1.3: {} + sumchecker@3.0.1: dependencies: debug: 4.4.3 transitivePeerDependencies: - supports-color + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-hyperlinks@2.3.0: + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + + supports-preserve-symlinks-flag@1.0.0: {} + tabbable@6.5.0: {} tailwind-merge@3.6.0: {} @@ -6303,6 +13307,20 @@ snapshots: mkdirp: 0.5.6 rimraf: 2.6.3 + terminal-link@2.1.1: + dependencies: + ansi-escapes: 4.3.2 + supports-hyperlinks: 2.3.0 + + terser@5.49.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.17.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + throat@5.0.0: {} + through2@4.0.2: dependencies: readable-stream: 3.6.2 @@ -6332,8 +13350,20 @@ snapshots: tmp@0.2.7: {} + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + toqr@0.1.1: {} + totalist@3.0.1: {} + tr46@0.0.3: {} + tree-kill@1.2.2: {} trim-lines@3.0.1: {} @@ -6344,21 +13374,93 @@ snapshots: dependencies: utf8-byte-length: 1.0.5 + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + tslib@2.8.1: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + type-fest@0.13.1: optional: true + type-fest@0.21.3: {} + + type-fest@0.7.1: {} + type-fest@2.19.0: {} + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.8: + dependencies: + call-bind: 1.0.9 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + typescript@6.0.3: {} + ua-parser-js@1.0.41: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + undici-types@7.16.0: {} undici-types@8.3.0: {} undici@6.27.0: {} + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3 @@ -6396,6 +13498,35 @@ snapshots: universalify@2.0.1: {} + unpipe@1.0.0: {} + + unrs-resolver@1.12.2: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.12.2 + '@unrs/resolver-binding-android-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-arm64': 1.12.2 + '@unrs/resolver-binding-darwin-x64': 1.12.2 + '@unrs/resolver-binding-freebsd-x64': 1.12.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.12.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.12.2 + '@unrs/resolver-binding-linux-loong64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-loong64-musl': 1.12.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.12.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.12.2 + '@unrs/resolver-binding-linux-x64-musl': 1.12.2 + '@unrs/resolver-binding-openharmony-arm64': 1.12.2 + '@unrs/resolver-binding-wasm32-wasi': 1.12.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.12.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.12.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.12.2 + untildify@4.0.0: {} unzipper@0.12.5: @@ -6406,6 +13537,35 @@ snapshots: graceful-fs: 4.2.11 node-int64: 0.4.0 + update-browserslist-db@1.2.3(browserslist@4.28.6): + dependencies: + browserslist: 4.28.6 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@19.2.17)(react@19.2.3): + dependencies: + react: 19.2.3 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + + use-latest-callback@0.2.6(react@19.2.3): + dependencies: + react: 19.2.3 + + use-sidecar@1.1.3(@types/react@19.2.17)(react@19.2.3): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.3 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 19.2.17 + use-sync-external-store@1.6.0(react@19.2.6): dependencies: react: 19.2.6 @@ -6414,6 +13574,23 @@ snapshots: util-deprecate@1.0.2: {} + utils-merge@1.0.1: {} + + uuid@7.0.3: {} + + validate-npm-package-name@5.0.1: {} + + vary@1.1.2: {} + + vaul@1.1.2(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@radix-ui/react-dialog': 1.1.19(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + vfile-location@5.0.3: dependencies: '@types/unist': 3.0.3 @@ -6429,25 +13606,25 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3): + vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0): dependencies: '@oxc-project/types': 0.138.0 '@oxlint/plugins': 1.68.0 - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9) '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 '@vitest/spy': 4.1.9 '@vitest/utils': 4.1.9 - '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3) - oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) - oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) + oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) oxlint-tsgolint: 0.24.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)' - vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' + vitest: 4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) optionalDependencies: '@voidzero-dev/vite-plus-darwin-arm64': 0.2.2 '@voidzero-dev/vite-plus-darwin-x64': 0.2.2 @@ -6487,25 +13664,25 @@ snapshots: - utf-8-validate - yaml - vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3): + vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0): dependencies: '@oxc-project/types': 0.138.0 '@oxlint/plugins': 1.68.0 - '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))) - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))) + '@vitest/browser': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9) '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 '@vitest/spy': 4.1.9 '@vitest/utils': 4.1.9 - '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3) - oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) - oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + '@voidzero-dev/vite-plus-core': 0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0) + oxfmt: 0.57.0(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) + oxlint: 1.72.0(oxlint-tsgolint@0.24.0)(vite-plus@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) oxlint-tsgolint: 0.24.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' - vitest: 4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9))(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' + vitest: 4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) optionalDependencies: '@voidzero-dev/vite-plus-darwin-arm64': 0.2.2 '@voidzero-dev/vite-plus-darwin-x64': 0.2.2 @@ -6545,10 +13722,10 @@ snapshots: - utf-8-validate - yaml - vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)): + vitest@4.1.9(@types/node@24.12.4)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -6565,18 +13742,18 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.12.4 - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9) transitivePeerDependencies: - msw - vitest@4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9))(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)): + vitest@4.1.9(@types/node@26.1.1)(@vitest/browser-preview@4.1.9)(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)): dependencies: '@vitest/expect': 4.1.9 - '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)) + '@vitest/mocker': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)) '@vitest/pretty-format': 4.1.9 '@vitest/runner': 4.1.9 '@vitest/snapshot': 4.1.9 @@ -6593,14 +13770,26 @@ snapshots: tinyexec: 1.2.4 tinyglobby: 0.2.17 tinyrainbow: 3.1.0 - vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(typescript@6.0.3)' + vite: '@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0)' why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 26.1.1 - '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@24.12.4)(jiti@2.7.0)(typescript@6.0.3))(vitest@4.1.9) + '@vitest/browser-preview': 4.1.9(@voidzero-dev/vite-plus-core@0.2.2(@types/node@26.1.1)(jiti@2.7.0)(terser@5.49.0)(typescript@6.0.3)(yaml@2.9.0))(vitest@4.1.9) transitivePeerDependencies: - msw + vlq@1.0.1: {} + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + warn-once@0.1.1: {} + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + web-namespaces@2.0.1: {} webcrypto-core@1.9.2: @@ -6611,6 +13800,58 @@ snapshots: asn1js: 3.0.10 tslib: 2.8.1 + webidl-conversions@3.0.1: {} + + whatwg-fetch@3.6.20: {} + + whatwg-url-minimum@0.1.2: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.2.0 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.22 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.22: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + which@2.0.2: dependencies: isexe: 2.0.0 @@ -6628,6 +13869,8 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + word-wrap@1.2.5: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -6636,10 +13879,22 @@ snapshots: wrappy@1.0.2: {} + ws@7.5.13: {} + ws@8.18.3: {} ws@8.21.0: {} + xcode@3.0.1: + dependencies: + simple-plist: 1.3.1 + uuid: 7.0.3 + + xml2js@0.6.0: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + xml2js@0.6.2: dependencies: sax: 1.6.0 @@ -6651,10 +13906,14 @@ snapshots: y18n@5.0.8: {} + yallist@3.1.1: {} + yallist@4.0.0: {} yallist@5.0.0: {} + yaml@2.9.0: {} + yargs-parser@21.1.1: {} yargs@17.7.3: @@ -6678,6 +13937,12 @@ snapshots: yocto-queue@0.1.0: {} + zod-validation-error@4.0.2(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod@3.25.76: {} + zustand@5.0.14(@types/react@19.2.17)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6)): optionalDependencies: '@types/react': 19.2.17 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b77f9296..f91c30fb 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,6 +7,9 @@ allowBuilds: electron-winstaller: false esbuild: true node-pty: true + # ESLint's resolver ships ready-to-use platform packages. Its optional build + # step is not needed and stays disabled under the repository safety policy. + unrs-resolver: false catalog: "@types/node": 24.12.4 diff --git a/scripts/check-release-consistency.mjs b/scripts/check-release-consistency.mjs index 86a2d7b3..88387f4a 100644 --- a/scripts/check-release-consistency.mjs +++ b/scripts/check-release-consistency.mjs @@ -14,6 +14,7 @@ export const RELEASE_CONTRACT_PATHS = [ "README.md", "SECURITY.md", "THIRD_PARTY_NOTICES.md", + "apps/companion/app.json", "apps/desktop/src/target-manager.ts", "apps/site/src/docs/content.ts", "apps/site/src/release.ts", @@ -336,14 +337,33 @@ export function collectReleaseConsistencyErrors(files, releaseTag) { errors.push(`${path} version ${JSON.stringify(manifest.version)} does not match ${version}`); } } - const mobileManifest = parseJson(files, "apps/mobile/package.json", errors); - if ( - mobileManifest?.scripts?.["check:android:debug"] !== - "pnpm sync:android && node ./scripts/run-gradle.mjs testDebugUnitTest assembleDebug lintDebug" - ) { - errors.push( - "apps/mobile/package.json must run Android JVM tests, debug compilation, and lint in the pre-merge check", - ); + const companionManifestPath = "apps/companion/package.json"; + const companionManifest = parseJson(files, companionManifestPath, errors); + if (companionManifest?.scripts?.["check:android:native"] !== "node scripts/android-build.mjs check") { + errors.push(`${companionManifestPath} must run the native Expo Android pre-merge check`); + } + if (companionManifest?.scripts?.["build:android:release"] !== "node scripts/android-build.mjs release") { + errors.push(`${companionManifestPath} must run the signed native Expo Android release build`); + } + + const companionConfigPath = "apps/companion/app.json"; + const companionConfig = parseJson(files, companionConfigPath, errors)?.expo; + const [major, minor, patch] = version.split(".").map(Number); + const nativeBuildNumber = major * 1_000_000 + minor * 10_000 + patch; + if (companionConfig?.version !== version) { + errors.push(`${companionConfigPath} Expo version must be ${version}`); + } + if (companionConfig?.android?.package !== androidIdentity?.applicationId) { + errors.push(`${companionConfigPath} Android package must preserve the release identity`); + } + if (companionConfig?.android?.versionCode !== nativeBuildNumber) { + errors.push(`${companionConfigPath} Android versionCode must be ${nativeBuildNumber}`); + } + if (companionConfig?.ios?.bundleIdentifier !== androidIdentity?.applicationId) { + errors.push(`${companionConfigPath} iOS bundle identifier must preserve the release identity`); + } + if (companionConfig?.ios?.buildNumber !== String(nativeBuildNumber)) { + errors.push(`${companionConfigPath} iOS buildNumber must be ${nativeBuildNumber}`); } if (releaseTag !== undefined && releaseTag !== expectedTag) { @@ -758,7 +778,7 @@ export function collectReleaseConsistencyErrors(files, releaseTag) { "actions/setup-java@c1e323688fd81a25caa38c78aa6df2d33d3e20d9", "android-actions/setup-android@9fc6c4e9069bf8d3d10b2204b1fb8f6ef7065407", 'sdkmanager --install "platforms;android-36" "build-tools;36.0.0"', - "pnpm --filter @t4-code/mobile check:android:debug", + "pnpm --filter @t4-code/companion check:android:native", ]) { requireText(ciWorkflow, expected, ".github/workflows/ci.yml", errors); } @@ -793,7 +813,7 @@ export function collectReleaseConsistencyErrors(files, releaseTag) { "T4_ANDROID_KEYSTORE_PASSWORD", "T4_ANDROID_KEY_ALIAS", "T4_ANDROID_KEY_PASSWORD", - "pnpm --filter @t4-code/mobile build:android:release", + "pnpm --filter @t4-code/companion build:android:release", "node scripts/inspect-android-release.mjs", "node scripts/inspect-linux-update.mjs", '--metadata "$metadata"', diff --git a/scripts/check-release-consistency.test.mjs b/scripts/check-release-consistency.test.mjs index bcc513a8..7e8cf28e 100644 --- a/scripts/check-release-consistency.test.mjs +++ b/scripts/check-release-consistency.test.mjs @@ -487,11 +487,11 @@ test("deploys release site source only after artifact publication", () => { assert.ok( ciWorkflow.includes('sdkmanager --install "platforms;android-36" "build-tools;36.0.0"'), ); - assert.ok(ciWorkflow.includes("pnpm --filter @t4-code/mobile check:android:debug")); + assert.ok(ciWorkflow.includes("pnpm --filter @t4-code/companion check:android:native")); assert.ok(!ciWorkflow.includes("T4_ANDROID_KEYSTORE_BASE64")); assert.equal( - JSON.parse(files.get("apps/mobile/package.json")).scripts["check:android:debug"], - "pnpm sync:android && node ./scripts/run-gradle.mjs testDebugUnitTest assembleDebug lintDebug", + JSON.parse(files.get("apps/companion/package.json")).scripts["check:android:native"], + "node scripts/android-build.mjs check", ); assert.ok(releaseWorkflow.includes("github.ref == 'refs/heads/main'")); @@ -540,7 +540,7 @@ test("deploys release site source only after artifact publication", () => { ]) { assert.ok(!releaseVerify.includes(duplicate)); } - assert.ok(releaseWorkflow.includes("pnpm --filter @t4-code/mobile build:android:release")); + assert.ok(releaseWorkflow.includes("pnpm --filter @t4-code/companion build:android:release")); assert.ok(releaseWorkflow.includes("T4_ANDROID_KEYSTORE_BASE64")); assert.ok(releaseWorkflow.includes("node scripts/inspect-android-release.mjs")); assert.ok(releaseWorkflow.includes('--metadata "$metadata"')); diff --git a/scripts/inspect-android-release.mjs b/scripts/inspect-android-release.mjs index a3c20a4c..88e3f74d 100644 --- a/scripts/inspect-android-release.mjs +++ b/scripts/inspect-android-release.mjs @@ -155,7 +155,7 @@ function validateUniversalMetadata(metadata, expected) { export function validateAndroidRelease({ contract, packageVersion, - mobilePackageVersion, + companionPackageVersion, apkName, apkFileNames, outputMetadata, @@ -164,8 +164,8 @@ export function validateAndroidRelease({ signerOutput, }) { validateIdentityContract(contract); - if (mobilePackageVersion !== packageVersion) { - fail(`mobile package version ${mobilePackageVersion} does not match root package version ${packageVersion}`); + if (companionPackageVersion !== packageVersion) { + fail(`companion package version ${companionPackageVersion} does not match root package version ${packageVersion}`); } const versionCode = deriveAndroidVersionCode(packageVersion); @@ -250,7 +250,7 @@ export function inspectAndroidRelease({ repoRoot, apk, metadata, aapt, apksigner const contract = readJson(resolve(repoRoot, ".github/android-release-identity.json"), "Android release identity contract"); const rootPackage = readJson(resolve(repoRoot, "package.json"), "root package manifest"); - const mobilePackage = readJson(resolve(repoRoot, "apps/mobile/package.json"), "mobile package manifest"); + const companionPackage = readJson(resolve(repoRoot, "apps/companion/package.json"), "companion package manifest"); const outputMetadata = readJson(metadata, "Android output metadata"); const apkFileNames = readdirSync(dirname(apk)).filter((file) => file.endsWith(".apk")).sort(); const badgingOutput = execFileSync(aapt, ["dump", "badging", apk], { encoding: "utf8" }); @@ -260,7 +260,7 @@ export function inspectAndroidRelease({ repoRoot, apk, metadata, aapt, apksigner return validateAndroidRelease({ contract, packageVersion: rootPackage.version, - mobilePackageVersion: mobilePackage.version, + companionPackageVersion: companionPackage.version, apkName: basename(apk), apkFileNames, outputMetadata, diff --git a/scripts/packaging.test.mjs b/scripts/packaging.test.mjs index 9f750fba..cd5c3bbb 100644 --- a/scripts/packaging.test.mjs +++ b/scripts/packaging.test.mjs @@ -43,7 +43,7 @@ function androidReleaseFixture(overrides = {}) { return { contract, packageVersion, - mobilePackageVersion: overrides.mobilePackageVersion ?? packageVersion, + companionPackageVersion: overrides.companionPackageVersion ?? packageVersion, apkName: overrides.apkName ?? "app-release.apk", apkFileNames: overrides.apkFileNames ?? ["app-release.apk"], outputMetadata: overrides.outputMetadata ?? { @@ -173,10 +173,10 @@ test("Android release identity is public, pinned, and wired into the release wor assert.match(releaseWorkflow, /--aapt "\$build_tools\/aapt"/u); assert.match(releaseWorkflow, /--apksigner "\$build_tools\/apksigner"/u); const androidDebugGate = releaseWorkflow.indexOf( - "pnpm --filter @t4-code/mobile check:android:debug", + "pnpm --filter @t4-code/companion check:android:native", ); const androidReleaseBuild = releaseWorkflow.indexOf( - "pnpm --filter @t4-code/mobile build:android:release", + "pnpm --filter @t4-code/companion build:android:release", ); assert.ok(androidDebugGate >= 0, "release workflow must run the Android debug verification gate"); assert.ok(androidDebugGate < androidReleaseBuild, "Android verification must precede the signed build"); diff --git a/scripts/tailnet-gateway.mjs b/scripts/tailnet-gateway.mjs index 59512a9f..96e97093 100644 --- a/scripts/tailnet-gateway.mjs +++ b/scripts/tailnet-gateway.mjs @@ -24,14 +24,6 @@ const SERVICE_UNIT_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_.@:-]{0,127}$/u; const OMP_SOCKET_NAME = /^\.appserver-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\.sock$/u; -// Capacitor v8 serves bundled assets from these origins when its documented -// default hostname and platform schemes are used. Keep this list exact: an -// Origin header identifies a browser context, not a signed mobile binary. -export const CAPACITOR_NATIVE_ORIGINS = Object.freeze([ - "https://localhost", - "capacitor://localhost", -]); - const MIME_TYPES = new Map([ [".css", "text/css; charset=utf-8"], [".html", "text/html; charset=utf-8"], @@ -76,23 +68,6 @@ export function normalizeAllowedOrigin(value) { return url.origin; } -export function normalizeNativeAllowedOrigins(value = CAPACITOR_NATIVE_ORIGINS) { - if (!Array.isArray(value)) { - throw new Error("T4_NATIVE_ALLOWED_ORIGINS must be an array"); - } - const origins = value.map((origin) => requiredText(origin, "T4_NATIVE_ALLOWED_ORIGINS", 128)); - const unique = new Set(origins); - if ( - unique.size !== CAPACITOR_NATIVE_ORIGINS.length || - !CAPACITOR_NATIVE_ORIGINS.every((origin) => unique.has(origin)) - ) { - throw new Error( - `T4_NATIVE_ALLOWED_ORIGINS must contain exactly ${CAPACITOR_NATIVE_ORIGINS.join(", ")}`, - ); - } - return [...CAPACITOR_NATIVE_ORIGINS]; -} - export function normalizeDeploymentIdentity(value) { const identity = requiredText(value, "T4_DEPLOYMENT_IDENTITY", 80); if (!/^sha256:[0-9a-f]{64}$/u.test(identity)) { @@ -488,7 +463,6 @@ export async function startTailnetGateway(input) { listenHost: input.listenHost ?? "127.0.0.1", listenPort: input.listenPort ?? DEFAULT_PORT, allowedOrigin: normalizeAllowedOrigin(input.allowedOrigin), - nativeAllowedOrigins: normalizeNativeAllowedOrigins(input.nativeAllowedOrigins), label: input.label ?? "OMP on this Tailnet host", deploymentIdentity: normalizeDeploymentIdentity(input.deploymentIdentity), heartbeatIntervalMs: input.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS, @@ -513,7 +487,6 @@ export async function startTailnetGateway(input) { if (!Number.isSafeInteger(value) || value < 1) throw new Error(`Tailnet gateway ${name} is invalid`); } if (typeof options.startSupervisor !== "function") throw new Error("profile supervisor starter is invalid"); - const allowedSocketOrigins = new Set([options.allowedOrigin, ...options.nativeAllowedOrigins]); const profilesById = new Map(options.profiles.map((profile) => [profile.id, profile])); const startState = { starts: new Map(), lastStarts: new Map() }; let closed = false; @@ -576,7 +549,7 @@ export async function startTailnetGateway(input) { rejectUpgrade(socket, "404 Not Found", "Not found"); return; } - if (!allowedSocketOrigins.has(request.headers.origin)) { + if (request.headers.origin !== options.allowedOrigin) { rejectUpgrade(socket, "403 Forbidden", "Origin not allowed"); return; } @@ -663,10 +636,6 @@ export function optionsFromEnvironment(environment = process.env) { listenHost: environment.T4_GATEWAY_HOST ?? "127.0.0.1", listenPort: port, allowedOrigin: environment.T4_ALLOWED_ORIGIN, - nativeAllowedOrigins: - environment.T4_NATIVE_ALLOWED_ORIGINS === undefined - ? undefined - : environment.T4_NATIVE_ALLOWED_ORIGINS.split(","), label: environment.T4_HOST_LABEL ?? "OMP on this Tailnet host", deploymentIdentity: environment.T4_DEPLOYMENT_IDENTITY, profileRoutes, diff --git a/scripts/tailnet-gateway.test.mjs b/scripts/tailnet-gateway.test.mjs index 7373e33f..518c3ad5 100644 --- a/scripts/tailnet-gateway.test.mjs +++ b/scripts/tailnet-gateway.test.mjs @@ -9,15 +9,12 @@ import { test } from "node:test"; import WebSocket, { WebSocketServer } from "ws"; import { - CAPACITOR_NATIVE_ORIGINS, cacheControlForStaticPath, injectBackendConfig, normalizeAllowedOrigin, normalizeDeploymentIdentity, - normalizeNativeAllowedOrigins, normalizeProfileRoutes, supervisorCommandForRoute, - optionsFromEnvironment, resolveAppSocket, safeStaticPath, startTailnetGateway, @@ -116,21 +113,6 @@ test("origin validation accepts only explicit Tailscale HTTPS origins", () => { } }); -test("native origin validation is pinned to Capacitor's Android and iOS defaults", () => { - assert.deepEqual(normalizeNativeAllowedOrigins(), ["https://localhost", "capacitor://localhost"]); - assert.deepEqual(normalizeNativeAllowedOrigins(["capacitor://localhost", "https://localhost"]), [ - ...CAPACITOR_NATIVE_ORIGINS, - ]); - for (const value of [ - ["*"], - ["null"], - ["http://localhost", "capacitor://localhost"], - ["https://localhost", "capacitor://localhost", "https://mobile.example.com"], - ]) { - assert.throws(() => normalizeNativeAllowedOrigins(value), /must contain exactly/u); - } -}); - test("deployment identity accepts only an exact immutable SHA-256 token", () => { assert.equal(normalizeDeploymentIdentity(DEPLOYMENT_IDENTITY), DEPLOYMENT_IDENTITY); for (const value of ["latest", `sha256:${"B".repeat(64)}`, `sha256:${"b".repeat(63)}`]) { @@ -138,20 +120,6 @@ test("deployment identity accepts only an exact immutable SHA-256 token", () => } }); -test("gateway environment parses the explicit comma-separated native origin set", () => { - const options = optionsFromEnvironment({ - T4_ALLOWED_ORIGIN: ALLOWED_ORIGIN, - T4_NATIVE_ALLOWED_ORIGINS: "https://localhost,capacitor://localhost", - T4_DEPLOYMENT_IDENTITY: DEPLOYMENT_IDENTITY, - XDG_RUNTIME_DIR: "/run/user/1000", - }); - assert.deepEqual(normalizeNativeAllowedOrigins(options.nativeAllowedOrigins), [ - "https://localhost", - "capacitor://localhost", - ]); - assert.equal(options.deploymentIdentity, DEPLOYMENT_IDENTITY); -}); - test("backend injection is explicit, credential-free, and script-safe", () => { const source = ""; const injected = injectBackendConfig(source, { @@ -241,7 +209,7 @@ test("gateway serves configured app and reports real upstream health", async () } }); -test("gateway rejects cross-origin sockets and bridges only the web and native allowlist", async () => { +test("gateway accepts only the exact Tailnet origin used by web and Expo native clients", async () => { const running = await fixture(); try { for (const origin of ["https://attacker.example-tailnet.ts.net", "null", "*"]) { @@ -258,7 +226,7 @@ test("gateway rejects cross-origin sockets and bridges only the web and native a assert.equal(deniedStatus, 403); } - for (const origin of [ALLOWED_ORIGIN, ...CAPACITOR_NATIVE_ORIGINS]) { + for (const origin of [ALLOWED_ORIGIN]) { const allowed = new WebSocket(`${running.url.replace("http", "ws")}/v1/ws`, { headers: { Origin: origin }, }); diff --git a/scripts/tailnet-service.mjs b/scripts/tailnet-service.mjs index ea539b64..19316cbb 100644 --- a/scripts/tailnet-service.mjs +++ b/scripts/tailnet-service.mjs @@ -14,7 +14,6 @@ const CONFIG_VERSION = 1; const MAX_OUTPUT_BYTES = 64 * 1024; const MAX_TEXT = 4_096; const PROFILE_ROUTES_ENVIRONMENT_KEY = "T4_PROFILE_ROUTES"; -const CAPACITOR_NATIVE_ORIGINS = Object.freeze(["https://localhost", "capacitor://localhost"]); function fail(message) { throw new Error(message); @@ -94,19 +93,6 @@ function normalizeServiceOrigin(value) { return url.origin; } -function normalizeServiceNativeOrigins(value = CAPACITOR_NATIVE_ORIGINS) { - if (!Array.isArray(value)) fail("native allowed origins must be an array"); - const origins = value.map((origin) => cleanText(origin, "native allowed origin", 128)); - const unique = new Set(origins); - if ( - unique.size !== CAPACITOR_NATIVE_ORIGINS.length || - !CAPACITOR_NATIVE_ORIGINS.every((origin) => unique.has(origin)) - ) { - fail(`native allowed origins must contain exactly ${CAPACITOR_NATIVE_ORIGINS.join(", ")}`); - } - return [...CAPACITOR_NATIVE_ORIGINS]; -} - export function servicePaths({ platform = process.platform, homeDirectory = homedir(), @@ -166,7 +152,6 @@ export function validateServiceConfig(input) { webRoot: absolutePath(input.webRoot ?? join(sourceRoot, "apps", "web", "dist"), "web root"), appSocket: absolutePath(input.appSocket, "OMP appserver socket"), allowedOrigin: normalizeServiceOrigin(input.allowedOrigin), - nativeAllowedOrigins: normalizeServiceNativeOrigins(input.nativeAllowedOrigins), port: gatewayPort(input.port ?? DEFAULT_GATEWAY_PORT), label: cleanText(input.label ?? "OMP on this Tailnet host", "host label", 128), deploymentIdentity: deploymentIdentity(input.deploymentIdentity), @@ -192,7 +177,6 @@ function escapeXml(value) { function gatewayEnvironment(config) { return { T4_ALLOWED_ORIGIN: config.allowedOrigin, - T4_NATIVE_ALLOWED_ORIGINS: config.nativeAllowedOrigins.join(","), T4_GATEWAY_HOST: "127.0.0.1", T4_GATEWAY_PORT: String(config.port), T4_WEB_ROOT: config.webRoot, @@ -735,7 +719,6 @@ async function inspect(paths, options = {}) { console.log(`health: ${healthResult.ok ? "healthy" : "unhealthy"}`); console.log(`local URL: http://127.0.0.1:${config.port}`); console.log(`allowed origin: ${config.allowedOrigin}`); - console.log(`native origins: ${config.nativeAllowedOrigins.join(", ")}`); if (deploymentIdentityState !== undefined) console.log(`deployment identity: ${deploymentIdentityState}`); if (supervisorResult?.stderr) console.log(`diagnostics: ${supervisorResult.stderr}`); if ( diff --git a/scripts/tailnet-service.test.mjs b/scripts/tailnet-service.test.mjs index ee17df8f..1b9a71cf 100644 --- a/scripts/tailnet-service.test.mjs +++ b/scripts/tailnet-service.test.mjs @@ -28,7 +28,6 @@ const CONFIG = { webRoot: "/home/alice/t4-code/apps/web/dist", appSocket: "/run/user/1000/omp/appserver.sock", allowedOrigin: "https://workstation.example-tailnet.ts.net:8445", - nativeAllowedOrigins: ["https://localhost", "capacitor://localhost"], port: DEFAULT_GATEWAY_PORT, label: "Alice's workstation", deploymentIdentity: `sha256:${"a".repeat(64)}`, @@ -36,17 +35,10 @@ const CONFIG = { test("service config requires an exact Tailnet HTTPS origin and absolute local paths", () => { assert.deepEqual(validateServiceConfig(CONFIG), CONFIG); - const legacyConfig = { ...CONFIG }; - delete legacyConfig.nativeAllowedOrigins; - assert.deepEqual(validateServiceConfig(legacyConfig), CONFIG); assert.throws( () => validateServiceConfig({ ...CONFIG, allowedOrigin: "https://public.example.com" }), /Tailscale HTTPS origin/u, ); - assert.throws( - () => validateServiceConfig({ ...CONFIG, nativeAllowedOrigins: ["*"] }), - /must contain exactly/u, - ); assert.throws( () => validateServiceConfig({ ...CONFIG, webRoot: "apps/web/dist" }), /absolute path/u, @@ -113,10 +105,7 @@ test("Linux paths are user-scoped and the systemd unit is shell-free and loopbac const unit = renderSystemdUnit({ ...CONFIG, label: 'host "$(touch /tmp/nope)" 100%' }); assert.match(unit, /ExecStart="\/opt\/node\/bin\/node" "\/home\/alice\/t4-code\/scripts\/tailnet-gateway\.mjs"/u); assert.match(unit, /Environment="T4_GATEWAY_HOST=127\.0\.0\.1"/u); - assert.match( - unit, - /Environment="T4_NATIVE_ALLOWED_ORIGINS=https:\/\/localhost,capacitor:\/\/localhost"/u, - ); + assert.doesNotMatch(unit, /T4_NATIVE_ALLOWED_ORIGINS/u); assert.match(unit, /Environment="T4_HOST_LABEL=host \\"\\\$\(touch \/tmp\/nope\)\\" 100%%"/u); assert.match(unit, /Environment="T4_DEPLOYMENT_IDENTITY=sha256:a{64}"/u); assert.match(unit, /NoNewPrivileges=true/u); @@ -163,10 +152,7 @@ test("macOS paths and launch agent preserve argv and environment as XML data", ( assert.match(plist, /ProgramArguments<\/key>/u); assert.match(plist, /\/opt\/node\/bin\/node<\/string>/u); assert.match(plist, /T4_GATEWAY_HOST<\/key>\s+127\.0\.0\.1<\/string>/u); - assert.match( - plist, - /T4_NATIVE_ALLOWED_ORIGINS<\/key>\s+https:\/\/localhost,capacitor:\/\/localhost<\/string>/u, - ); + assert.doesNotMatch(plist, /T4_NATIVE_ALLOWED_ORIGINS/u); assert.match(plist, /A&B <host><\/string>/u); assert.match(plist, /T4_DEPLOYMENT_IDENTITY<\/key>\s+sha256:a{64}<\/string>/u); assert.match(plist, /Umask<\/key>63<\/integer>/u);