diff --git a/.github/workflows/release-cut.yml b/.github/workflows/release-cut.yml index 87c383fea69..4352af6e065 100644 --- a/.github/workflows/release-cut.yml +++ b/.github/workflows/release-cut.yml @@ -310,13 +310,16 @@ jobs: # already exist — that's a real conflict (someone tagged manually # over a shipped version) and needs human attention. if git rev-parse "v$new" >/dev/null 2>&1; then - # Use the REST API, not `gh release view`: the latter returns - # "release not found" for drafts *and* for absent releases, - # which would cause us to "recover" over a draft that a human - # is preparing. The API distinguishes them. - release_state="$(gh api \ + # Why: use the REST API, not `gh release view`. `gh api` returns + # `true`/`false` for a real release, while a 404 means the tag is + # orphaned and can be safely re-dispatched. Capture failures + # explicitly so the 404 payload never leaks into the state check. + release_state="missing" + if gh api \ "repos/$GITHUB_REPOSITORY/releases/tags/v$new" \ - --jq '.draft' 2>/dev/null || echo "missing")" + --jq '.draft' >"$RUNNER_TEMP/release-state" 2>/dev/null; then + release_state="$(cat "$RUNNER_TEMP/release-state")" + fi case "$release_state" in missing|true) echo "::warning::Tag v$new already exists but no published release is attached — recovering by re-dispatching release.yml against the existing tag." diff --git a/.oxlintrc.json b/.oxlintrc.json index dce97c91041..eb968c10e61 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -7,7 +7,6 @@ "rules": { "react/jsx-no-duplicate-props": "error", "react/jsx-no-undef": "error", - "react/jsx-uses-vars": "error", "react/no-children-prop": "error", "react/no-danger-with-children": "error", "react/no-direct-mutation-state": "error", diff --git a/AGENTS.md b/AGENTS.md index 683b216a3d1..f0979fb2159 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,5 +1,9 @@ # AGENTS.md +## Design System + +All UI work — layout, color, typography, spacing, component selection, UX behavior — must follow [`docs/STYLEGUIDE.md`](./docs/STYLEGUIDE.md). Use the tokens defined in `src/renderer/src/assets/main.css` (the canonical source) and the shadcn primitives in `src/renderer/src/components/ui/`. Don't invent new color values, font sizes, or shadow tiers when a documented one already covers the role. When STYLEGUIDE.md is silent, follow the resolution order in its final section. + ## Code Comments: Document the "Why", Briefly When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining **why** the code behaves the way it does. diff --git a/config/patches/@xterm__addon-ligatures@0.11.0-beta.198.patch b/config/patches/@xterm__addon-ligatures@0.11.0-beta.216.patch similarity index 100% rename from config/patches/@xterm__addon-ligatures@0.11.0-beta.198.patch rename to config/patches/@xterm__addon-ligatures@0.11.0-beta.216.patch diff --git a/config/patches/node-pty@1.1.0.patch b/config/patches/node-pty@1.1.0.patch index 4ff713f6c2b..f52544bcf1c 100644 --- a/config/patches/node-pty@1.1.0.patch +++ b/config/patches/node-pty@1.1.0.patch @@ -74,17 +74,276 @@ index 1ec12f796a822c78fba9ad7f6448c3987e325c23..cec8b67aef02f8199e5606a0d257088b var DEFAULT_NAME = 'xterm'; var DESTROY_SOCKET_TIMEOUT_MS = 200; diff --git a/src/unix/pty.cc b/src/unix/pty.cc -index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..c17decbb7bc06f10d7b5d26317bd68fdc8c4e339 100644 +index 7b4b9e1f990fbf95b51528bb56dc9717f5b87532..61f39f0cbb91faa2c515f35d2ca850564e6368d9 100644 --- a/src/unix/pty.cc +++ b/src/unix/pty.cc -@@ -778,8 +778,8 @@ done: - posix_spawn_file_actions_destroy(&acts); - posix_spawnattr_destroy(&attrs); +@@ -23,7 +23,9 @@ + #include + #include + #include ++#include + #include ++#include + #include + + #include +@@ -237,13 +239,23 @@ pty_getproc(int, char *); + #endif + + #if defined(__APPLE__) || defined(__OpenBSD__) ++struct pty_spawn_error { ++ const char* step; ++ int errnum; ++ std::string detail_name; ++ std::string detail_value; ++}; ++ ++static std::string ++pty_format_spawn_error(const pty_spawn_error&); ++ + static void + pty_posix_spawn(char** argv, char** env, + const struct termios *termp, + const struct winsize *winp, + int* master, + pid_t* pid, +- int* err); ++ pty_spawn_error* err); + #endif + + struct DelBuf { +@@ -367,10 +379,11 @@ Napi::Value PtyFork(const Napi::CallbackInfo& info) { + argv[i + 3] = strdup(arg.c_str()); + } + +- int err = -1; +- pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err); +- if (err != 0) { +- throw Napi::Error::New(napiEnv, "posix_spawnp failed."); ++ pty_spawn_error spawn_error = { NULL, 0, "", "" }; ++ pty_posix_spawn(argv, env, term, &winp, &master, &pid, &spawn_error); ++ if (spawn_error.errnum != 0) { ++ std::string spawn_message = pty_format_spawn_error(spawn_error); ++ throw Napi::Error::New(napiEnv, spawn_message); + } + if (pty_nonblock(master) == -1) { + throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking."); +@@ -684,15 +697,73 @@ pty_getproc(int fd, char *tty) { + #endif + + #if defined(__APPLE__) ++static const char* ++pty_errno_name(int errnum) { ++ switch (errnum) { ++ case E2BIG: return "E2BIG"; ++ case EACCES: return "EACCES"; ++ case EAGAIN: return "EAGAIN"; ++ case EMFILE: return "EMFILE"; ++ case ENFILE: return "ENFILE"; ++ case ENOENT: return "ENOENT"; ++ case ENOMEM: return "ENOMEM"; ++ default: return "errno"; ++ } ++} ++ ++static void ++pty_set_spawn_error(pty_spawn_error* err, ++ const char* step, ++ int errnum, ++ const char* detail_name = NULL, ++ const char* detail_value = NULL) { ++ err->step = step; ++ err->errnum = errnum; ++ err->detail_name = detail_name ? detail_name : ""; ++ err->detail_value = detail_value ? detail_value : ""; ++} ++ ++static std::string ++pty_format_spawn_error(const pty_spawn_error& err) { ++ char errno_buf[64]; ++ snprintf(errno_buf, sizeof(errno_buf), "%d", err.errnum); ++ ++ std::string message = "node-pty: "; ++ message += err.step ? err.step : "unknown"; ++ message += " failed: "; ++ message += pty_errno_name(err.errnum); ++ message += " (errno "; ++ message += errno_buf; ++ message += ", "; ++ message += strerror(err.errnum); ++ message += ")"; ++ ++ if (!err.detail_name.empty()) { ++ message += " - "; ++ message += err.detail_name; ++ message += "='"; ++ message += err.detail_value; ++ message += "'"; ++ } ++ ++ return message; ++} ++ + static void + pty_posix_spawn(char** argv, char** env, + const struct termios *termp, + const struct winsize *winp, + int* master, + pid_t* pid, +- int* err) { +- int low_fds[3]; ++ pty_spawn_error* err) { ++ int low_fds[3] = {-1, -1, -1}; + size_t count = 0; ++ int res = -1; ++ int slave = -1; ++ posix_spawn_file_actions_t acts; ++ bool acts_initialized = false; ++ posix_spawnattr_t attrs; ++ bool attrs_initialized = false; + + for (; count < 3; count++) { + low_fds[count] = posix_openpt(O_RDWR); +@@ -706,80 +777,118 @@ pty_posix_spawn(char** argv, char** env, + POSIX_SPAWN_SETSID; + *master = posix_openpt(O_RDWR); + if (*master == -1) { +- return; ++ pty_set_spawn_error(err, "posix_openpt", errno); ++ goto done; ++ } ++ ++ res = grantpt(*master); ++ if (res == -1) { ++ pty_set_spawn_error(err, "grantpt", errno); ++ goto done; + } + +- int res = grantpt(*master) || unlockpt(*master); ++ res = unlockpt(*master); + if (res == -1) { +- return; ++ pty_set_spawn_error(err, "unlockpt", errno); ++ goto done; + } + + // Use TIOCPTYGNAME instead of ptsname() to avoid threading problems. +- int slave; + char slave_pty_name[128]; + res = ioctl(*master, TIOCPTYGNAME, slave_pty_name); + if (res == -1) { +- return; ++ pty_set_spawn_error(err, "ioctl_TIOCPTYGNAME", errno); ++ goto done; + } + + slave = open(slave_pty_name, O_RDWR | O_NOCTTY); + if (slave == -1) { +- return; ++ pty_set_spawn_error(err, "open_slave", errno, "slave", slave_pty_name); ++ goto done; + } + + if (termp) { + res = tcsetattr(slave, TCSANOW, termp); + if (res == -1) { +- return; ++ pty_set_spawn_error(err, "tcsetattr", errno, "slave", slave_pty_name); ++ goto done; + }; + } + + if (winp) { + res = ioctl(slave, TIOCSWINSZ, winp); + if (res == -1) { +- return; ++ pty_set_spawn_error(err, "ioctl_TIOCSWINSZ", errno, "slave", slave_pty_name); ++ goto done; + } + } + +- posix_spawn_file_actions_t acts; +- posix_spawn_file_actions_init(&acts); ++ res = posix_spawn_file_actions_init(&acts); ++ if (res != 0) { ++ pty_set_spawn_error(err, "posix_spawn_file_actions_init", res); ++ goto done; ++ } ++ acts_initialized = true; + posix_spawn_file_actions_adddup2(&acts, slave, STDIN_FILENO); + posix_spawn_file_actions_adddup2(&acts, slave, STDOUT_FILENO); + posix_spawn_file_actions_adddup2(&acts, slave, STDERR_FILENO); + posix_spawn_file_actions_addclose(&acts, slave); + posix_spawn_file_actions_addclose(&acts, *master); + +- posix_spawnattr_t attrs; +- posix_spawnattr_init(&attrs); +- *err = posix_spawnattr_setflags(&attrs, flags); +- if (*err != 0) { ++ res = posix_spawnattr_init(&attrs); ++ if (res != 0) { ++ pty_set_spawn_error(err, "posix_spawnattr_init", res); ++ goto done; ++ } ++ attrs_initialized = true; ++ res = posix_spawnattr_setflags(&attrs, flags); ++ if (res != 0) { ++ pty_set_spawn_error(err, "posix_spawnattr_setflags", res); + goto done; + } + + sigset_t signal_set; + /* Reset all signal the child to their default behavior */ + sigfillset(&signal_set); +- *err = posix_spawnattr_setsigdefault(&attrs, &signal_set); +- if (*err != 0) { ++ res = posix_spawnattr_setsigdefault(&attrs, &signal_set); ++ if (res != 0) { ++ pty_set_spawn_error(err, "posix_spawnattr_setsigdefault", res); + goto done; + } + + /* Reset the signal mask for all signals */ + sigemptyset(&signal_set); +- *err = posix_spawnattr_setsigmask(&attrs, &signal_set); +- if (*err != 0) { ++ res = posix_spawnattr_setsigmask(&attrs, &signal_set); ++ if (res != 0) { ++ pty_set_spawn_error(err, "posix_spawnattr_setsigmask", res); + goto done; + } + + do +- *err = posix_spawn(pid, argv[0], &acts, &attrs, argv, env); +- while (*err == EINTR); ++ res = posix_spawn(pid, argv[0], &acts, &attrs, argv, env); ++ while (res == EINTR); ++ if (res != 0) { ++ pty_set_spawn_error(err, "posix_spawn", res, "helper", argv[0]); ++ } + done: +- posix_spawn_file_actions_destroy(&acts); +- posix_spawnattr_destroy(&attrs); ++ if (acts_initialized) { ++ posix_spawn_file_actions_destroy(&acts); ++ } ++ if (attrs_initialized) { ++ posix_spawnattr_destroy(&attrs); ++ } ++ if (slave != -1) { ++ close(slave); ++ } ++ if (err->errnum != 0 && *master != -1) { ++ close(*master); ++ *master = -1; ++ } - for (; count > 0; count--) { - close(low_fds[count]); + for (size_t i = 0; i <= count && i < 3; i++) { -+ close(low_fds[i]); ++ if (low_fds[i] != -1) { ++ close(low_fds[i]); ++ } } } #endif diff --git a/config/scripts/dev-fresh-profile.sh b/config/scripts/dev-fresh-profile.sh new file mode 100755 index 00000000000..3579c840faa --- /dev/null +++ b/config/scripts/dev-fresh-profile.sh @@ -0,0 +1,43 @@ +#!/usr/bin/env bash +# Launch `pn dev` with a fresh, isolated userData profile so the app behaves +# like a first-time install (onboarding overlay paints, no persisted repos, +# no saved sessions). Your real `orca-dev` profile is left untouched. +# +# Usage: +# ./config/scripts/dev-fresh-profile.sh # ephemeral temp profile, deleted on exit +# ./config/scripts/dev-fresh-profile.sh --keep # keep the profile dir after exit +# ORCA_FRESH_PROFILE_DIR=/some/path ./config/scripts/dev-fresh-profile.sh # use a fixed dir +set -euo pipefail + +KEEP=0 +for arg in "$@"; do + case "$arg" in + --keep) KEEP=1 ;; + -h|--help) + sed -n '2,9p' "$0" | sed 's/^# \{0,1\}//' + exit 0 ;; + *) + echo "Unknown argument: $arg" >&2 + echo "Usage: $0 [--keep] [--help]" >&2 + exit 2 ;; + esac +done + +PROFILE_DIR="${ORCA_FRESH_PROFILE_DIR:-$(mktemp -d "${TMPDIR:-/tmp}/orca-fresh-profile.XXXXXXXX")}" +mkdir -p "$PROFILE_DIR" + +cleanup() { + if [[ "$KEEP" -eq 0 && -z "${ORCA_FRESH_PROFILE_DIR:-}" ]]; then + # Guard rm -rf against accidental empty/unrelated PROFILE_DIR. + [[ -n "${PROFILE_DIR:-}" && -d "$PROFILE_DIR" && "$PROFILE_DIR" == */orca-fresh-profile* ]] || return 0 + rm -rf "$PROFILE_DIR" + echo "[dev-fresh-profile] removed $PROFILE_DIR" + else + echo "[dev-fresh-profile] kept $PROFILE_DIR" + fi +} +trap cleanup EXIT + +echo "[dev-fresh-profile] using userData=$PROFILE_DIR" +# Don't exec — we need the EXIT trap to fire so the temp profile gets cleaned up. +ORCA_DEV_USER_DATA_PATH="$PROFILE_DIR" pnpm dev diff --git a/config/scripts/run-electron-vite-dev.mjs b/config/scripts/run-electron-vite-dev.mjs index 1bfcb87c6de..408206b9838 100644 --- a/config/scripts/run-electron-vite-dev.mjs +++ b/config/scripts/run-electron-vite-dev.mjs @@ -1,5 +1,7 @@ import { spawn } from 'node:child_process' +import { createHash } from 'node:crypto' import { chmodSync, mkdirSync, writeFileSync } from 'node:fs' +import net from 'node:net' import { createRequire } from 'node:module' import path from 'node:path' import { fileURLToPath } from 'node:url' @@ -61,7 +63,83 @@ if (process.env.ORCA_SKIP_DEV_CLI_PREPARE !== '1') { const electronViteCli = process.env.ORCA_ELECTRON_VITE_CLI || path.join(path.dirname(require.resolve('electron-vite/package.json')), 'bin', 'electron-vite.js') -const forwardedArgs = ['dev', ...process.argv.slice(2)] + +// Why: every `pn dev` should be attachable from agent-browser/playwright-cli +// without manual port juggling. Pick a best-effort deterministic port per +// worktree; falls back to a probe sweep if the deterministic pick or its +// neighbors are busy (multiple worktrees may share a machine). +const forwardedRaw = process.argv.slice(2) +function isPortFree(port) { + return new Promise((resolve) => { + const srv = net.createServer() + srv.once('error', () => { + // Why: error fires before listen binds; close() may throw — swallow it + // so the handle is released without leaking listeners across 64 probes. + try { + srv.close() + } catch {} + resolve(false) + }) + srv.once('listening', () => srv.close(() => resolve(true))) + srv.listen(port, '127.0.0.1') + }) +} +async function pickDebugPort() { + // Why: 32 bits of SHA1 (vs 16) reduces truncation bias; modulo 200 still + // collides routinely across many worktrees, hence the probe sweep below. + const seed = parseInt(createHash('sha1').update(repoRoot).digest('hex').slice(0, 8), 16) + const base = 9333 + (seed % 200) // deterministic base in 9333..9532; probe sweeps up to base+63 + for (let i = 0; i < 64; i++) { + const p = base + i + if (await isPortFree(p)) { + return p + } + } + return null +} +function parseDebugPortEnv(raw) { + const n = Number.parseInt(raw, 10) + if (!Number.isInteger(n) || n < 1 || n > 65535 || String(n) !== raw.trim()) { + return null + } + return n +} +// Why: exact match (or `=` form) avoids false positives on hypothetical +// `--remote-debugging-port-*` flags; the bare flag also covers the +// space-separated form. `--remote-debugging-pipe` opts into pipe-based +// debugging — don't fight the user's choice by injecting a port. +const userPassedPort = forwardedRaw.some( + (a) => a === '--remote-debugging-port' || a.startsWith('--remote-debugging-port=') || a === '--remote-debugging-pipe' +) +// Why: --help/--version exit immediately; binding a probe socket and printing +// a debug-port line would be noise. +const isHelpOrVersion = forwardedRaw.some((a) => a === '--help' || a === '-h' || a === '--version') +let forwardedExtras = [] +if (!userPassedPort && !isHelpOrVersion) { + const envPortRaw = process.env.REMOTE_DEBUGGING_PORT + let port = null + if (envPortRaw) { + port = parseDebugPortEnv(envPortRaw) + if (port === null) { + console.error( + `[orca-dev] Ignoring invalid REMOTE_DEBUGGING_PORT=${JSON.stringify(envPortRaw)}; falling back to probe.` + ) + } + } + if (port === null) { + port = await pickDebugPort() + } + if (port !== null) { + forwardedExtras = [`--remote-debugging-port=${port}`] + // Why: stderr keeps stdout clean for downstream parsing; log uses + // 127.0.0.1 to match the interface we actually probed (localhost may + // resolve to ::1 on IPv6-first hosts). + console.error(`[orca-dev] Remote debugging on http://127.0.0.1:${port}`) + } else { + console.error('[orca-dev] No free debug port found in sweep; starting without --remote-debugging-port.') + } +} +const forwardedArgs = ['dev', ...forwardedRaw, ...forwardedExtras] const child = spawn(process.execPath, [electronViteCli, ...forwardedArgs], { stdio: 'inherit', env: process.env, diff --git a/docs/STYLEGUIDE.md b/docs/STYLEGUIDE.md new file mode 100644 index 00000000000..b41f9f61a74 --- /dev/null +++ b/docs/STYLEGUIDE.md @@ -0,0 +1,277 @@ +# Orca UI Style Guide + +This is the **UI/visual design** doc for Orca — color tokens, typography, component selection, and UX rules. It is *not* an architecture doc; for system-level design see code and inline comments. Token values live in `src/renderer/src/assets/main.css` (canonical); this file documents the *roles and rules* for using them. + +## Overview + +Orca is an Electron desktop app for orchestrating coding agents across git worktrees. The visual identity is **monochrome and quiet** — neutral grays carry the chrome, color is reserved for state (selection ring, destructive, git decorations). The product spends most of its time hosting other people's tools (Monaco, xterm, Markdown previews), so Orca's own UI should recede and frame. + +When in doubt: + +- Reach for **muted/accent/border** before reaching for color. +- Reach for **CSS variables** before hardcoding hex. +- Match the nearest **shadcn primitive** before writing custom CSS. + +## Source of truth + +| Concern | Canonical location | +| -------------------- | ------------------------------------------------ | +| Color tokens | `src/renderer/src/assets/main.css` (`:root`, `.dark`) | +| Tailwind theme bindings | Same file, `@theme inline { … }` block | +| Component primitives | `src/renderer/src/components/ui/` (shadcn-style) | +| App typography / scrollbars / titlebar chrome | Same `main.css` | + +Never hardcode a hex value in component code if a variable already covers it. If a new token is needed, add it to `main.css` (both `:root` and `.dark`), expose it in the `@theme inline` block, then use it. + +## Color roles + +Tokens come in pairs: a **surface** and a **foreground** that meets contrast on it. Always use them together. + +| Role | Use it for | Don't use it for | +| ---------------------------------- | ------------------------------------------------------------- | --------------------------------------------- | +| `background` / `foreground` | App canvas, default text | Cards, popovers, sidebar (have their own) | +| `card` / `card-foreground` | Panels lifted off the canvas | The canvas itself | +| `popover` / `popover-foreground` | Floating menus, dropdowns, hovercards | Inline UI | +| `primary` / `primary-foreground` | The single affirmative action in a flow (Save, Confirm) | Decorative accents; hover states; secondary actions | +| `secondary` / `secondary-foreground` | Lower-emphasis actions next to a primary | The affirmative action | +| `muted` / `muted-foreground` | De-emphasized text, captions, placeholders, disabled chrome | Body copy; primary actions | +| `accent` / `accent-foreground` | Hover/active backgrounds for ghost buttons and list rows | Solid filled buttons (use `secondary` instead) | +| `destructive` / `destructive-foreground` | Delete, discard, irreversible-action buttons; error states | Cancel buttons (Cancel is not destructive) | +| `border` | All hairlines: dividers, input outlines, card edges | Heavy emphasis; that's `ring` | +| `input` | Form field background only | Anywhere outside form fields | +| `ring` | Focus-visible outlines, active selection halos | Persistent decoration | +| `sidebar` (+ variants) | The worktree sidebar and its children | Other panels | +| `editor-surface` | Background of Monaco / markdown editor panes | App chrome | + +The `sidebar` family expands into `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, and `--sidebar-ring` — use them inside the worktree sidebar so its hover/selected/focus states stay consistent and don't bleed into other panels. `editor-surface` is its own token (not just `background`) because Monaco and the markdown editor have a slightly darker surface in dark mode to match VS Code conventions; reach for it whenever you're rendering an editor pane. + +### Git decoration colors + +For diff status, file-tree decorations, and the changes view, use the git decoration tokens (mirroring VS Code's palette so users transferring from VS Code aren't surprised): + +| Token | State | +| ------------------------------ | ------------------------------ | +| `--git-decoration-added` | Added / new | +| `--git-decoration-modified` | Modified | +| `--git-decoration-deleted` | Deleted | +| `--git-decoration-renamed` | Renamed | +| `--git-decoration-untracked` | Untracked | +| `--git-decoration-copied` | Copied | + +Use these *only* for git status. Don't reuse them for unrelated state colors — that breaks the convention. + +### List rows: hover, selected, current + +A common point of drift. Use these conventions for any list-style row (worktrees, command palette items, settings nav): + +- **Idle:** transparent background. +- **Hover:** `bg-accent` (in the worktree sidebar, `bg-sidebar-accent`). +- **Keyboard-selected (cmdk highlight):** `data-[selected=true]:bg-accent` plus a `border-border` outline so the active row stays visible while the user types. The `data-selected` attribute is set by `cmdk` automatically. +- **Persistent "current" / "active" row** (e.g. the worktree the user is viewing): also `bg-accent`, *plus* a `data-current="true"` attribute so CSS or future styling can distinguish it from the cmdk highlight. +- **Don't:** hardcode `bg-[#ededed]` / `bg-[#333333]` or invent a "selected" color. The accent token already adapts to light/dark and matches the rest of the app. + +### Color mixing + +When you need a tint (e.g. a 12% primary wash on hover), use `color-mix` against the existing token, not a new hex: + +```css +background: color-mix(in srgb, var(--primary) 12%, var(--background)); +``` + +This keeps light/dark parity automatic. + +## Typography + +- **Family:** `Geist` is loaded as a single variable woff2 (weight range 100–900). Always reach for `Geist` for sans, never `Inter` or system sans. +- **Mono:** `var(--font-mono)` — used for paths, terminal-adjacent UI, code, and anywhere monospace conveys "this is literal." +- **Body letter-spacing:** `0.01em` (set globally on `body`). Don't override per component. +- **Sizes:** Tailwind's default scale. Common sizes in this repo: + - 11px (uppercase meta, sidebar headers, captions) — pair with `font-weight: 600` and `text-transform: uppercase` and `letter-spacing: 0.05em` for category labels. + - 12px (sub-text, paths, secondary content) + - 13px (sidebar items, dense list rows) + - 14px (default body, button text in `default` size) + +## Radius + +`--radius: 0.625rem` (10px) is the base; the rest are computed (`--radius-sm` = 0.6×, `--radius-md` = 0.8×, `--radius-lg` = 1×, `--radius-xl` = 1.4×, etc.). Buttons and inputs use `rounded-md`; the `Card` primitive uses `rounded-xl`; badges use `rounded-full`. Match the existing primitive's radius rather than introducing a new one. + +## Elevation & shadows + +Orca uses shadows sparingly. Three levels in practice: + +1. **Inset hairline** — `border` + `border` token. The default. Almost everything sits at this level. +2. **Subtle lift** — `shadow-xs` + a single-token border. Outline buttons, embedded cards. +3. **Floating** — `0 10px 24px rgba(0, 0, 0, 0.18)`. Popovers, popups that escape the editor surface. Reserved. + +Don't add a fourth level. If something needs more emphasis than "floating," you're probably reaching for the focus `ring` instead. + +## Components + +Use the shadcn primitives in `src/renderer/src/components/ui/` before writing anything custom. The shadcn-style wrappers in this folder follow a consistent pattern: + +- Most carry a `data-slot=""` attribute on their root for CSS targeting — do not strip it. (The non-shadcn helpers in this folder — `sonner`, `repo-multi-combobox`, `team-multi-combobox` — don't follow this pattern and shouldn't be modeled when adding new primitives that should.) +- Use `cn()` for class merging. Pass user `className` last so callers can override. +- Use `class-variance-authority` (CVA) for variants when there are multiple. + +### Buttons (`button.tsx`) + +Variants in priority order: + +| Variant | Use case | +| ------------- | ---------------------------------------------------------------- | +| `default` | The single affirmative action in a flow. | +| `secondary` | Lower-emphasis sibling next to a `default`. | +| `outline` | Toolbar / standalone actions where a filled button feels heavy. | +| `ghost` | Icon buttons, list-row triggers, anywhere chrome should disappear. | +| `link` | Inline text actions inside paragraphs. | +| `destructive` | Delete, discard, irreversible. Never for Cancel. | + +Sizes: `default` (36px), `sm` (32px), `xs` (24px), `lg` (40px), plus `icon`, `icon-xs`, `icon-sm`, `icon-lg`. Match the size to the surrounding row height — don't drop a `default` button into a 28px toolbar. + +### Other primitives in this repo + +Browse `src/renderer/src/components/ui/` for the full list. Most wrap a Radix UI primitive — exceptions are `command` (wraps `cmdk`), `sonner` (wraps `sonner`), and the visual-only wrappers (`badge`, `button-group`, `card`, `input`) which apply tokens and Tailwind utilities directly. Never reimplement headless behavior; extend the existing wrapper. + +### Picking the right primitive + +When a control has multiple plausible primitives, use this fork: + +| You want… | Reach for | Don't use | +| -------------------------------------------------------- | -------------------------- | ---------------------------------------- | +| Hover-only label on an icon-only button | `Tooltip` | `HoverCard` (too heavy), title attr | +| Hover preview of richer content (avatar + summary) | `HoverCard` | `Tooltip` (no rich content) | +| Click-revealed menu with actions | `DropdownMenu` | `Popover` with hand-rolled list | +| Right-click contextual actions | `ContextMenu` | `DropdownMenu` (different invocation) | +| Click-revealed surface with arbitrary content (form, picker) | `Popover` | `Dialog` (it traps focus and dims) | +| Modal that demands a decision before you continue | `Dialog` | `Popover`, inline overlay | +| Drawer / panel sliding in from an edge | `Sheet` | `Dialog` centered | +| Single choice from a known list | `Select` | Custom listbox | +| Single choice with search / fuzzy filtering | `Command` inside `Popover` | `Select` (no search) | +| Multi-select with search | `repo-multi-combobox` / `team-multi-combobox` (mirror their pattern) | Roll a new one | +| Transient confirmation ("Saved", "Copied") | `sonner` toast | `Dialog`, inline banner | +| Persistent inline status ("3 errors") | inline text + `Badge` | toast (toasts disappear) | + +If you find yourself styling around a primitive (`` to act like a ``, or vice versa), stop and reconsider — the focus-management semantics differ and a future contributor will be misled by the mismatch. + +### Tooltips + +Tooltips exist to *name* a control whose meaning isn't obvious from its appearance. They are not the place to teach, persuade, or warn — anything users need to read while acting belongs in the visible UI. + +- **Use a tooltip when:** an icon-only button or compact chip needs a label. Toolbar icons, badges with abbreviations, truncated paths. +- **Don't use a tooltip when:** the control already has a visible label, the content is interactive (links, buttons), or the message is critical (errors, blocking warnings — those go inline). +- **Mounting:** the global `` lives at the App root. Don't nest a second `TooltipProvider` unless you need a different delay for a tightly-scoped surface. +- **Trigger pattern:** wrap the trigger element with `` so the tooltip's accessibility props attach to the button (not a wrapper span). This is required for keyboard focus to surface the tooltip. +- **Placement:** default `side="top" sideOffset={4}` — match the toolbar pattern in `sidebar/SidebarToolbar.tsx`. Pick a different side only when the default would clip against the viewport. +- **Shortcut chips inside tooltips:** if the action has a keyboard shortcut, append `` rather than baking the keys into the label string. The chips render correctly per platform; baked-in strings drift. + +```tsx + + + + + Settings + +``` + +### Icons + +Icons come from **`lucide-react`**. Don't import a second icon library. + +- **Default size:** `size-4` (16px). `Button` auto-applies this to any `` it contains via `[&_svg:not([class*='size-'])]:size-4`, so most call sites don't need to set a size on the icon. +- **`size-3` / `size-3.5`:** for metadata, captions, and dense list rows where 16px is too loud. +- **`size-7`+:** for featured/empty-state hero icons only. +- **Stroke width:** lucide's default 2px. Don't override per-icon. +- **Color:** inherit from surrounding text — `text-muted-foreground` for secondary, `text-destructive` for destructive, etc. Don't apply a token to the SVG directly when the parent already carries the right color. +- **Spinner:** the canonical loading icon is ``. For 3s+ multi-step work, prefer a label that names the stage ("Cloning…" → "Installing…") over an unlabeled spinner. See *UX rule 1*. + +### Keyboard shortcut chips + +Use **``** from `src/renderer/src/components/ShortcutKeyCombo.tsx`. It renders a consistent key-cap style and inserts a `+` separator on Windows/Linux (Mac shows adjacent glyphs, no separator). It does **not** transform key strings — the *caller* picks the platform-appropriate labels and passes them in: + +```tsx +const isMac = navigator.userAgent.includes('Mac') +const mod = isMac ? '⌘' : 'Ctrl' +const shift = isMac ? '⇧' : 'Shift' + +``` + +See `Landing.tsx` for the canonical pattern. Don't roll a one-off `` — kbd chips drift in shape and color across the app fast if everyone styles their own. + +**Where shortcuts surface in the UI:** + +- **Tooltips on icon buttons** — append the chip after the label, trailing. +- **Dropdown / context-menu items** — use `` (or its context-menu equivalent) for the right-aligned chip; don't position one yourself. +- **Never on Cancel, Dismiss, or `link`-variant inline actions** — see *UX rule 3*. + +**The label MUST match the actual binding for the platform.** If the keyboard handler reads `metaKey` on Mac and `ctrlKey` elsewhere, the chip must show `⌘` on Mac and `Ctrl` elsewhere. Mismatched chips are worse than no chip. + +### Form anatomy + +The pattern in `src/renderer/src/components/settings/SettingsFormControls.tsx` is the house style for any label + control + helper text. Match it for new forms: + +- **Outer stack:** `space-y-3` for full-section forms (`ThemePicker`); `space-y-2` for compact single-control fields (`ColorField`, `NumberField`). Pick by density, not preference. +- **Label group:** `space-y-1` containing `` deep links to the confirm screen so + // the same pairing flow runs whether the link arrived via QR scan, + // paste, AirDrop, Messages, or `xcrun simctl openurl`. getInitialURL + // covers cold-start (link tapped while app was closed); the listener + // covers warm-start (link tapped while app is in memory). + useEffect(() => { + function handleUrl(url: string) { + const code = extractPairCode(url) + if (code) { + router.push({ pathname: '/pair-confirm', params: { code } }) + } + } + + void Linking.getInitialURL().then((url) => { + if (url) handleUrl(url) + }) + + const sub = Linking.addEventListener('url', ({ url }) => handleUrl(url)) + return () => sub.remove() + }, [router]) + // Why: hide the native splash only once the navigation Stack has been laid // out — this is the earliest moment the user will see actual app content. // Previously the splash hid when a placeholder View rendered, leaving a @@ -36,32 +80,36 @@ export default function RootLayout() { }, []) return ( - - - - + + + + - - - - - - - - + > + + }} + /> + + + + + + + + + + + ) } diff --git a/mobile/app/about.tsx b/mobile/app/about.tsx index 088c6da211f..70a1effa44b 100644 --- a/mobile/app/about.tsx +++ b/mobile/app/about.tsx @@ -1,11 +1,25 @@ -import { View, Text, StyleSheet, Pressable, Linking } from 'react-native' +import { View, Text, StyleSheet, Pressable, Linking, Platform } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useRouter } from 'expo-router' import { ChevronLeft, Globe } from 'lucide-react-native' import Svg, { Path } from 'react-native-svg' +import Constants from 'expo-constants' import { OrcaLogo } from '../src/components/OrcaLogo' import { colors, spacing, typography } from '../src/theme/mobile-theme' +// Why: read version + native build identifier from expo-constants at +// runtime so the About screen never drifts out of sync with app.json. +// nativeBuildVersion is iOS buildNumber on iOS and versionCode on +// Android — different concepts, same role (monotonic native build id). +function getVersionLabel(): string { + const version = Constants.expoConfig?.version ?? '?.?.?' + const build = + Platform.OS === 'ios' + ? Constants.expoConfig?.ios?.buildNumber + : String(Constants.expoConfig?.android?.versionCode ?? '') + return build ? `v${version} (${build})` : `v${version}` +} + function GithubIcon({ size = 16, color = colors.textSecondary }) { return ( @@ -66,6 +80,8 @@ export default function AboutScreen() { @orca_build + + {getVersionLabel()} ) } @@ -141,5 +157,11 @@ const styles = StyleSheet.create({ height: StyleSheet.hairlineWidth, backgroundColor: colors.borderSubtle, marginHorizontal: spacing.md + }, + versionText: { + marginTop: spacing.lg, + textAlign: 'center', + fontSize: typography.metaSize, + color: colors.textMuted } }) diff --git a/mobile/app/h/[hostId]/accounts.tsx b/mobile/app/h/[hostId]/accounts.tsx new file mode 100644 index 00000000000..24a555b7f2c --- /dev/null +++ b/mobile/app/h/[hostId]/accounts.tsx @@ -0,0 +1,414 @@ +import { useEffect, useState, useCallback, useRef } from 'react' +import { + View, + Text, + StyleSheet, + Pressable, + ScrollView, + ActivityIndicator, + RefreshControl, + Alert +} from 'react-native' +import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' +import { useLocalSearchParams, useRouter } from 'expo-router' +import { ChevronLeft, Check, RefreshCw, User } from 'lucide-react-native' +import type { RpcClient } from '../../../src/transport/rpc-client' +import { loadHosts } from '../../../src/transport/host-store' +import { useHostClient } from '../../../src/transport/client-context' +import type { RpcSuccess } from '../../../src/transport/types' +import { colors, spacing, typography, radii } from '../../../src/theme/mobile-theme' +import { ClaudeIcon, OpenAIIcon } from '../../../src/components/AgentIcons' +import { + type AccountsSnapshot, + type ProviderKey, + getActiveProviderRateLimits, + getInactiveProviderUsage, + UsageBar +} from '../../../src/components/AccountUsage' + +export default function AccountsScreen() { + const router = useRouter() + const insets = useSafeAreaInsets() + const { hostId } = useLocalSearchParams<{ hostId: string }>() + + // Why: shared client per host. See docs/mobile-shared-client-per-host.md. + const { client, state: connState } = useHostClient(hostId) + const [hostName, setHostName] = useState('') + const [snapshot, setSnapshot] = useState(null) + const [error, setError] = useState(null) + const [refreshing, setRefreshing] = useState(false) + const [busyAccountId, setBusyAccountId] = useState(null) + const clientRef = useRef(null) + + useEffect(() => { + clientRef.current = client + }, [client]) + + useEffect(() => { + if (!hostId) return + let stale = false + void loadHosts().then((hosts) => { + if (stale) return + const host = hosts.find((h) => h.id === hostId) + if (!host) { + setError('Host not found') + return + } + setHostName(host.name) + }) + return () => { + stale = true + } + }, [hostId]) + + // Why: subscribe to streaming snapshot updates so usage bars refresh in + // place when the desktop's rate-limit poll completes (every 5 min) or + // when the user switches accounts. Falls back to a one-shot accounts.list + // if the subscription stream errors. + useEffect(() => { + if (!client || connState !== 'connected') return + const unsubscribe = client.subscribe('accounts.subscribe', null, (payload) => { + if (!payload || typeof payload !== 'object') return + const evt = payload as { type?: string; snapshot?: AccountsSnapshot } + if ((evt.type === 'ready' || evt.type === 'snapshot') && evt.snapshot) { + setSnapshot(evt.snapshot) + setError(null) + } + }) + return unsubscribe + }, [client, connState]) + + const refresh = useCallback(async () => { + if (!client) return + setRefreshing(true) + try { + const res = await client.sendRequest('accounts.list') + if (res.ok) { + setSnapshot((res as RpcSuccess).result as AccountsSnapshot) + setError(null) + } else { + setError(res.error.message) + } + } catch (e) { + setError(e instanceof Error ? e.message : String(e)) + } finally { + setRefreshing(false) + } + }, [client]) + + const selectAccount = useCallback( + async (provider: ProviderKey, accountId: string | null) => { + if (!client) return + setBusyAccountId(accountId ?? `${provider}:default`) + const method = provider === 'claude' ? 'accounts.selectClaude' : 'accounts.selectCodex' + try { + const res = await client.sendRequest(method, { accountId }) + if (!res.ok) { + Alert.alert('Could not switch account', res.error.message) + } else { + // Why: optimistic refresh — the streaming subscription will also + // emit, but a one-shot keeps the UI responsive even if the stream + // is temporarily disconnected. + await refresh() + } + } catch (e) { + Alert.alert('Could not switch account', e instanceof Error ? e.message : String(e)) + } finally { + setBusyAccountId(null) + } + }, + [client, refresh] + ) + + const renderProviderSection = (provider: ProviderKey, title: string) => { + if (!snapshot) return null + const state = provider === 'claude' ? snapshot.claude : snapshot.codex + const activeUsage = getActiveProviderRateLimits(snapshot, provider) + const Icon = provider === 'claude' ? ClaudeIcon : OpenAIIcon + return ( + + + + {title} + + + {/* System default row */} + [styles.row, pressed && styles.rowPressed]} + onPress={() => selectAccount(provider, null)} + disabled={busyAccountId !== null || connState !== 'connected'} + > + + System default + Use the agent's own login + + + {state.activeAccountId === null ? ( + + ) : busyAccountId === `${provider}:default` ? ( + + ) : null} + + + + {state.accounts.map((account) => { + const isActive = state.activeAccountId === account.id + const inactiveEntry = !isActive + ? getInactiveProviderUsage(snapshot, provider, account.id) + : null + const usage = isActive ? activeUsage : (inactiveEntry?.claude ?? null) + const isFetching = + (isActive && usage?.status === 'fetching') || + (!isActive && inactiveEntry?.isFetching === true) + const session = usage?.session + const weekly = usage?.weekly + return ( + + + [styles.row, pressed && styles.rowPressed]} + onPress={() => selectAccount(provider, account.id)} + disabled={busyAccountId !== null || connState !== 'connected' || isActive} + > + + + {account.email} + + + + + + {usage?.error ? ( + + {usage.error} + + ) : null} + + + {isActive ? ( + + ) : busyAccountId === account.id ? ( + + ) : null} + + + + ) + })} + + + ) + } + + return ( + + + router.back()}> + + + + Accounts + {hostName ? ( + + {hostName} + + ) : null} + + + {refreshing ? ( + + ) : ( + + )} + + + + + } + > + {connState !== 'connected' && !snapshot ? ( + + + Connecting to {hostName || 'host'}… + + ) : error && !snapshot ? ( + + {error} + + ) : !snapshot ? ( + + + Loading accounts… + + ) : ( + <> + {renderProviderSection('claude', 'Claude')} + {renderProviderSection('codex', 'Codex')} + + + + Add or re-authenticate accounts from desktop Settings → Accounts. + + + + )} + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.bgBase + }, + topRow: { + flexDirection: 'row', + alignItems: 'center', + paddingHorizontal: spacing.md, + paddingTop: spacing.sm, + paddingBottom: spacing.sm, + gap: spacing.sm + }, + backButton: { + width: 36, + height: 36, + borderRadius: 18, + alignItems: 'center', + justifyContent: 'center' + }, + iconButton: { + width: 36, + height: 36, + borderRadius: 18, + alignItems: 'center', + justifyContent: 'center' + }, + titleWrap: { + flex: 1 + }, + heading: { + fontSize: 20, + fontWeight: '700', + color: colors.textPrimary + }, + subheading: { + fontSize: typography.metaSize, + color: colors.textSecondary, + marginTop: 1 + }, + scroll: { + paddingHorizontal: spacing.lg, + paddingTop: spacing.sm + }, + section: { + marginBottom: spacing.xl + }, + sectionHeader: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.sm, + marginBottom: spacing.sm + }, + sectionHeading: { + fontSize: typography.metaSize, + fontWeight: '600', + color: colors.textSecondary, + textTransform: 'uppercase', + letterSpacing: 0.5 + }, + card: { + backgroundColor: colors.bgPanel, + borderRadius: radii.card, + overflow: 'hidden' + }, + row: { + flexDirection: 'row', + alignItems: 'center', + paddingVertical: spacing.md, + paddingHorizontal: spacing.md + 2 + }, + rowPressed: { + backgroundColor: colors.bgRaised + }, + rowMain: { + flex: 1, + gap: 4 + }, + // Why: fixed-width trailing slot so the usage bars in `rowMain` keep the + // same width whether or not the row is currently selected (otherwise the + // checkmark on the active account squeezes the bars narrower than the + // inactive rows above/below it). + rowTrailing: { + width: 24, + alignItems: 'flex-end', + justifyContent: 'center', + marginLeft: spacing.sm + }, + rowTitle: { + fontSize: typography.bodySize, + fontWeight: '500', + color: colors.textPrimary + }, + rowSubtitle: { + fontSize: typography.metaSize, + color: colors.textSecondary + }, + separator: { + height: StyleSheet.hairlineWidth, + backgroundColor: colors.borderSubtle, + marginHorizontal: spacing.md + }, + usageRow: { + flexDirection: 'row', + gap: spacing.md, + marginTop: 4 + }, + errorText: { + fontSize: typography.metaSize, + color: colors.statusRed + }, + placeholder: { + paddingVertical: spacing.xl * 2, + alignItems: 'center', + gap: spacing.sm + }, + placeholderText: { + fontSize: typography.bodySize, + color: colors.textSecondary + }, + footerHint: { + flexDirection: 'row', + alignItems: 'flex-start', + gap: spacing.sm, + paddingHorizontal: spacing.sm, + paddingTop: spacing.sm + }, + footerHintText: { + flex: 1, + fontSize: typography.metaSize, + color: colors.textMuted, + lineHeight: 18 + } +}) diff --git a/mobile/app/h/[hostId]/index.tsx b/mobile/app/h/[hostId]/index.tsx index e428d70e511..ff45db42fa9 100644 --- a/mobile/app/h/[hostId]/index.tsx +++ b/mobile/app/h/[hostId]/index.tsx @@ -24,11 +24,23 @@ import { Plus, Moon, Filter, - Check + Check, + UserCircle } from 'lucide-react-native' -import { connect, type RpcClient } from '../../../src/transport/rpc-client' +import type { RpcClient } from '../../../src/transport/rpc-client' import { loadHosts, updateLastConnected, removeHost } from '../../../src/transport/host-store' -import type { ConnectionState, RpcSuccess } from '../../../src/transport/types' +import { + useHostClient, + useCloseHost, + useForceReconnect, + useReconnectAttempt, + useLastConnectedAt +} from '../../../src/transport/client-context' +import { + classifyConnection, + type ConnectionVerdict +} from '../../../src/transport/connection-health' +import type { RpcSuccess } from '../../../src/transport/types' import { triggerMediumImpact } from '../../../src/platform/haptics' import { StatusDot } from '../../../src/components/StatusDot' import { NewWorktreeModal } from '../../../src/components/NewWorktreeModal' @@ -37,8 +49,10 @@ import { PickerModal, type PickerOption } from '../../../src/components/PickerMo import { ActionSheetContent } from '../../../src/components/ActionSheetModal' import { ConfirmModal } from '../../../src/components/ConfirmModal' import { BottomDrawer } from '../../../src/components/BottomDrawer' +import { ProtocolBlockScreen } from '../../../src/components/ProtocolBlockScreen' import { getCachedWorktrees } from '../../../src/cache/worktree-cache' -import { colors, spacing, typography } from '../../../src/theme/mobile-theme' +import { colors, radii, spacing, typography } from '../../../src/theme/mobile-theme' +import { evaluateCompat, type CompatVerdict } from '../../../src/transport/protocol-compat' import { loadPinnedIds, savePinnedIds, @@ -46,6 +60,15 @@ import { savePreferences } from '../../../src/storage/preferences' +// Why: locally-typed subset of the desktop's RuntimeStatus we read from +// `status.get`. Only the version fields matter to mobile today; everything +// else is opaque. Both fields are optional since pre-PR desktops won't +// return them — the compat evaluator handles undefined gracefully. +type DesktopStatus = { + protocolVersion?: number + minCompatibleMobileVersion?: number +} + type Worktree = { worktreeId: string repo: string @@ -75,13 +98,8 @@ type FilterState = { selectedRepos: Set } -const STATUS_LABELS: Record = { - connecting: 'Connecting…', - handshaking: 'Securing…', - connected: 'Connected', - disconnected: 'Disconnected', - reconnecting: 'Reconnecting…', - 'auth-failed': 'Auth failed' +function isErrorVerdict(v: ConnectionVerdict): boolean { + return v.kind === 'warning' || v.kind === 'unreachable' || v.kind === 'auth-failed' } const SORT_OPTIONS: PickerOption[] = [ @@ -244,13 +262,19 @@ export default function HostScreen() { const [initialCache] = useState(() => hostId ? (getCachedWorktrees(hostId) as Worktree[] | null) : null ) - const [client, setClient] = useState(null) + // Why: shared client per host owned by RpcClientProvider. See + // docs/mobile-shared-client-per-host.md. + const { client, state: connState } = useHostClient(hostId) + const reconnectAttempts = useReconnectAttempt(hostId) + const lastConnectedAt = useLastConnectedAt(hostId) const clientRef = useRef(null) - const [connState, setConnState] = useState('disconnected') + const closeHostClient = useCloseHost() + const forceReconnectHost = useForceReconnect() const [worktrees, setWorktrees] = useState(initialCache ?? []) const [worktreesLoaded, setWorktreesLoaded] = useState(initialCache != null) const [hostName, setHostName] = useState('') const [error, setError] = useState('') + const [compatVerdict, setCompatVerdict] = useState({ kind: 'ok' }) const [lastKnownWorktrees, setLastKnownWorktrees] = useState(initialCache ?? []) const [search, setSearch] = useState('') const [showSearch, setShowSearch] = useState(false) @@ -302,14 +326,17 @@ export default function HostScreen() { } }, [hostId]) + // Why: keep clientRef in sync so existing imperative call sites work + // unchanged. Also re-seed the cached worktree list on hostId change + // since the useState initializer only runs on first mount. + useEffect(() => { + clientRef.current = client + }, [client]) + useEffect(() => { - let disposed = false - let rpcClient: RpcClient | null = null - clientRef.current = null - setClient(null) - setConnState('connecting') setHostName('') setError('') + setCompatVerdict({ kind: 'ok' }) // Why: re-seed from the current host's cache on every hostId change. // The useState initializer only runs on first mount, so if Expo Router // reuses this screen with a different hostId, we must reset here. @@ -323,45 +350,20 @@ export default function HostScreen() { setWorktrees([]) setLastKnownWorktrees([]) } - - // Why: defer the RPC connection until after the navigation animation - // completes. Without this, connect() and loadHosts() block the JS - // thread during mount, delaying the screen transition by ~200-400ms. - // With cached worktrees the user sees content instantly; the live - // connection starts once the animation settles. - const rafId = requestAnimationFrame(() => { - if (disposed) return - void (async () => { - const hosts = await loadHosts() - const host = hosts.find((h) => h.id === hostId) - if (!host || disposed) { - if (!host && !disposed) setError('Host not found') - return - } - - rpcClient = connect(host.endpoint, host.deviceToken, host.publicKeyB64, (state) => { - if (!disposed) setConnState(state) - }) - if (disposed) { - rpcClient.close() - rpcClient = null - return - } - setHostName(host.name) - clientRef.current = rpcClient - setClient(rpcClient) - - await updateLastConnected(host.id) - })() - }) - - return () => { - disposed = true - cancelAnimationFrame(rafId) - rpcClient?.close() - if (clientRef.current === rpcClient) { - clientRef.current = null + if (!hostId) return + let stale = false + void loadHosts().then((hosts) => { + if (stale) return + const host = hosts.find((h) => h.id === hostId) + if (!host) { + setError('Host not found') + return } + setHostName(host.name) + void updateLastConnected(host.id) + }) + return () => { + stale = true } }, [hostId]) @@ -419,6 +421,47 @@ export default function HostScreen() { } }, [connState, fetchWorktrees]) + // Why: read desktop's protocol version from status.get on every connect + // and re-evaluate compatibility. If the desktop declares this mobile + // build too old (or vice versa via the local minimum), the host detail + // screen swaps to a hard-block screen instead of the worktree list. + // Today's compat constants are wide-open so this never blocks; the wire + // format is in place to flip a switch in a future release. + useEffect(() => { + if (connState !== 'connected' || !client) return + let cancelled = false + const requestClient = client + void (async () => { + try { + const response = await requestClient.sendRequest('status.get') + if (cancelled || clientRef.current !== requestClient) return + if (!response.ok) return + const status = (response as RpcSuccess).result as DesktopStatus + const verdict = evaluateCompat({ + desktopProtocolVersion: status.protocolVersion, + desktopMinCompatibleMobileVersion: status.minCompatibleMobileVersion + }) + setCompatVerdict(verdict) + if (verdict.kind === 'blocked') { + // Why: deterministic breadcrumb so support can confirm a block + // actually fired (vs a render bug). No PII — just version ints. + console.warn('[protocol-compat] blocked', { + reason: verdict.reason, + desktopVersion: verdict.desktopVersion, + requiredMobileVersion: verdict.requiredMobileVersion, + requiredDesktopVersion: verdict.requiredDesktopVersion + }) + } + } catch { + // Why: rare path — sendRequest can throw on transport tear-down. + // Treat as transient; verdict stays at previous value. + } + })() + return () => { + cancelled = true + } + }, [connState, client]) + useEffect(() => { if (connState !== 'connected') return const interval = setInterval(() => { @@ -498,9 +541,14 @@ export default function HostScreen() { const handleRemoveHost = useCallback(async () => { if (!hostId) return + // Why: close the shared client first so its WebSocket is gone before + // the host record disappears; otherwise the next loadHosts() the + // provider does (e.g. on remount) wouldn't find this host but the + // socket would still be open, leaking state. + closeHostClient(hostId) await removeHost(hostId) router.back() - }, [hostId, router]) + }, [hostId, router, closeHostClient]) const openWorktreeSession = useCallback( (item: Worktree) => { @@ -631,6 +679,10 @@ export default function HostScreen() { ) } + if (compatVerdict.kind === 'blocked') { + return + } + return ( @@ -638,15 +690,45 @@ export default function HostScreen() { router.back()}> - - - - {hostName || 'Host'} - - - {connState !== 'connected' && ( - {STATUS_LABELS[connState]} - )} + {(() => { + const headerVerdict = classifyConnection({ + state: connState, + reconnectAttempts, + lastConnectedAt + }) + return ( + <> + + + + {hostName || 'Host'} + + + {connState !== 'connected' && + (() => { + // Why: status label removed in favor of just the dot + + // Reconnect button — the home screen already surfaces the + // verdict text per host, and the dot color already + // signals severity here. Auth-failed routes through its + // dedicated banner so we still want to suppress the + // Reconnect button for that case. + const verdict = headerVerdict + const isError = isErrorVerdict(verdict) + const showReconnectButton = isError && hostId && verdict.kind !== 'auth-failed' + if (!showReconnectButton) return null + return ( + void forceReconnectHost(hostId!)} + hitSlop={8} + > + Reconnect + + ) + })()} + + ) + })()} {/* Filter/sort/group toolbar */} @@ -682,6 +764,17 @@ export default function HostScreen() { + router.push(`/h/${hostId}/accounts`)} + disabled={connState !== 'connected'} + > + + + setShowNewWorktree(true)} @@ -1086,9 +1179,18 @@ const styles = StyleSheet.create({ fontWeight: '600', color: colors.textPrimary }, - statusText: { - color: colors.textSecondary, - fontSize: typography.metaSize + reconnectButton: { + paddingVertical: 4, + paddingHorizontal: spacing.sm, + borderRadius: radii.button, + backgroundColor: colors.bgPanel, + borderWidth: 1, + borderColor: colors.borderSubtle + }, + reconnectButtonText: { + color: colors.textPrimary, + fontSize: typography.metaSize, + fontWeight: '600' }, authBanner: { backgroundColor: colors.bgPanel, diff --git a/mobile/app/h/[hostId]/session/[worktreeId].tsx b/mobile/app/h/[hostId]/session/[worktreeId].tsx index 97c5601337e..3ddf7c523c9 100644 --- a/mobile/app/h/[hostId]/session/[worktreeId].tsx +++ b/mobile/app/h/[hostId]/session/[worktreeId].tsx @@ -1,4 +1,6 @@ import { useState, useEffect, useRef, useCallback } from 'react' +import { Animated, AppState, type AppStateStatus } from 'react-native' +import * as Clipboard from 'expo-clipboard' import { View, Text, @@ -15,12 +17,20 @@ import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context' import { useLocalSearchParams, useRouter } from 'expo-router' import AsyncStorage from '@react-native-async-storage/async-storage' import { ArrowUp, ChevronLeft, Monitor, Plus, Smartphone } from 'lucide-react-native' -import { connect, type RpcClient } from '../../../../src/transport/rpc-client' +import type { RpcClient } from '../../../../src/transport/rpc-client' import { loadHosts } from '../../../../src/transport/host-store' +import { useHostClient } from '../../../../src/transport/client-context' import type { ConnectionState, RpcSuccess } from '../../../../src/transport/types' -import { triggerMediumImpact } from '../../../../src/platform/haptics' +import { + triggerMediumImpact, + triggerSelection, + triggerSuccess, + triggerError, + triggerEdgeBump +} from '../../../../src/platform/haptics' import { TerminalWebView, + type TerminalModes, type TerminalWebViewHandle } from '../../../../src/terminal/TerminalWebView' import { StatusDot } from '../../../../src/components/StatusDot' @@ -48,15 +58,22 @@ type TerminalCreateResult = { type MobileDisplayMode = 'auto' | 'phone' | 'desktop' -type AccessoryKey = { label: string; bytes: string; accessibilityLabel?: string } +type AccessoryKey = { + label: string + bytes: string + accessibilityLabel?: string + repeatable?: boolean +} const ACCESSORY_KEYS: AccessoryKey[] = [ { label: 'Esc', bytes: '\x1b' }, { label: 'Tab', bytes: '\t' }, - { label: '↑', bytes: '\x1b[A' }, - { label: '↓', bytes: '\x1b[B' }, - { label: '←', bytes: '\x1b[D' }, - { label: '→', bytes: '\x1b[C' }, + { label: '⌫', bytes: '\x7f', accessibilityLabel: 'Backspace', repeatable: true }, + { label: 'Del', bytes: '\x1b[3~', accessibilityLabel: 'Forward delete', repeatable: true }, + { label: '↑', bytes: '\x1b[A', repeatable: true }, + { label: '↓', bytes: '\x1b[B', repeatable: true }, + { label: '←', bytes: '\x1b[D', repeatable: true }, + { label: '→', bytes: '\x1b[C', repeatable: true }, { label: 'Ctrl+C', bytes: '\x03', accessibilityLabel: 'Interrupt terminal' }, { label: 'Ctrl+D', bytes: '\x04', accessibilityLabel: 'Send EOF' }, { label: 'Ctrl+L', bytes: '\x0c', accessibilityLabel: 'Clear screen' }, @@ -81,12 +98,22 @@ function TerminalPaneView({ handle, active, onRef, - onWebReady + onWebReady, + onSelectionMode, + onSelectionCopy, + onSelectionEvicted, + onModesChanged, + onHaptic }: { handle: string active: boolean onRef: (handle: string, ref: TerminalWebViewHandle | null) => void onWebReady: (handle: string) => void + onSelectionMode: (handle: string, active: boolean) => void + onSelectionCopy: (handle: string, text: string) => void + onSelectionEvicted: (handle: string) => void + onModesChanged: (handle: string, modes: TerminalModes) => void + onHaptic: (kind: 'selection' | 'success' | 'error' | 'edge-bump') => void }) { const setRef = useCallback( (ref: TerminalWebViewHandle | null) => { @@ -104,6 +131,11 @@ function TerminalPaneView({ ref={setRef} style={styles.terminalWebView} onWebReady={() => onWebReady(handle)} + onSelectionMode={(a) => onSelectionMode(handle, a)} + onSelectionCopy={(t) => onSelectionCopy(handle, t)} + onSelectionEvicted={() => onSelectionEvicted(handle)} + onModesChanged={(m) => onModesChanged(handle, m)} + onHaptic={onHaptic} /> ) @@ -123,8 +155,9 @@ export default function SessionScreen() { }>() const router = useRouter() const insets = useSafeAreaInsets() - const [client, setClient] = useState(null) - const [connState, setConnState] = useState('disconnected') + // Why: shared client per host owned by RpcClientProvider. See + // docs/mobile-shared-client-per-host.md. + const { client, state: connState } = useHostClient(hostId) const [terminals, setTerminals] = useState([]) const [terminalsLoaded, setTerminalsLoaded] = useState(false) const [input, setInput] = useState('') @@ -144,6 +177,14 @@ export default function SessionScreen() { // Why: server-authoritative display mode per terminal. The runtime is the // single source of truth — this state is populated from subscribe responses. const [terminalModes, setTerminalModes] = useState>(new Map()) + const [selectModeActive, setSelectModeActive] = useState(false) + const [canPaste, setCanPaste] = useState(false) + const [toastMessage, setToastMessage] = useState(null) + const toastOpacityRef = useRef(new Animated.Value(0)) + // Why: WebView pushes terminal modes (bracketed-paste, alt-screen) on every + // change so paste reads a synchronous snapshot — no round-trip required. + const ptyModesRef = useRef>(new Map()) + const initialModesSeenRef = useRef>(new Set()) const deviceTokenRef = useRef(null) const clientRef = useRef(null) // Why: measured once from TerminalWebView on mount, then passed with every @@ -161,6 +202,13 @@ export default function SessionScreen() { const webReadyHandlesRef = useRef>(new Set()) const activeHandleRef = useRef(null) const subscribeSeqRef = useRef>(new Map()) + // Why: server-side layout state machine emits a monotonic seq on every + // applyLayout. Track the highest seq we've observed per handle and drop + // any scrollback/resized event with a strictly older seq — these are + // late-arriving events from a superseded layout (e.g. phone-fit dims + // landing after the user toggled to desktop). Drops below `>20`-window + // gap reset (treat as a fresh subscription, e.g. server restart). + const layoutSeqRef = useRef>(new Map()) const sendingRef = useRef(false) // Why: tracks the pixel height of the terminal frame so measureFitDimensions // can use the exact container height instead of relying on window.innerHeight, @@ -178,6 +226,10 @@ export default function SessionScreen() { terminalUnsubsRef.current.delete(handle) subscribingHandlesRef.current.delete(handle) subscribeSeqRef.current.set(handle, (subscribeSeqRef.current.get(handle) ?? 0) + 1) + // Why: a fresh subscription will land on a new server-side state machine + // run (or the same one with a higher seq); reset the high-water mark so + // the first scrollback isn't accidentally dropped as stale. + layoutSeqRef.current.delete(handle) }, []) const clearTerminalCache = useCallback(() => { @@ -189,6 +241,7 @@ export default function SessionScreen() { initializedHandlesRef.current.clear() webReadyHandlesRef.current.clear() subscribeSeqRef.current.clear() + layoutSeqRef.current.clear() for (const term of terminalRefs.current.values()) { term.clear() } @@ -224,10 +277,6 @@ export default function SessionScreen() { const seq = (subscribeSeqRef.current.get(handle) ?? 0) + 1 subscribeSeqRef.current.set(handle, seq) - console.log( - `[mobile-fit] subscribeToTerminal handle=${handle} seq=${seq} viewport=${viewportRef.current ? `${viewportRef.current.cols}x${viewportRef.current.rows}` : 'none'} measured=${viewportMeasuredRef.current}` - ) - // Why: server handles auto-fit on subscribe — no terminal.focus call needed. // The viewport is embedded in the subscribe params so the server resizes // the PTY before serializing scrollback. This eliminates the focus→safeFit @@ -242,43 +291,118 @@ export default function SessionScreen() { (result) => { if (subscribeSeqRef.current.get(handle) !== seq) return const data = result as Record + // Why: stale-event filter. Server-side state machine bumps a + // monotonic seq on every applyLayout. Drop `resized` events + // whose seq is strictly older than what we've already observed + // for this handle — they're late-arriving from a superseded + // layout. `scrollback` is the response to a fresh subscribe, + // so it always resets the high-water mark regardless of seq + // (post-WS-reconnect or post-resubscribe the server may emit + // scrollback at a seq lower than what we'd seen pre-reconnect; + // dropping it would leave the user with a blank terminal). + const eventSeq = typeof data.seq === 'number' ? data.seq : null + if (eventSeq != null && data.type === 'resized') { + const last = layoutSeqRef.current.get(handle) + if (last != null && eventSeq < last && last - eventSeq <= 20) { + console.log('[fit][session] DROP-stale-seq', { + handle: handle.slice(-8), + type: data.type, + eventSeq, + lastSeq: last, + cols: data.cols, + rows: data.rows, + displayMode: data.displayMode + }) + return + } + layoutSeqRef.current.set(handle, eventSeq) + } else if (eventSeq != null && data.type === 'scrollback') { + layoutSeqRef.current.set(handle, eventSeq) + } if (data.type === 'scrollback') { - console.log( - `[mobile-fit] scrollback handle=${handle} cols=${data.cols} rows=${data.rows} displayMode=${data.displayMode} hasSerialized=${!!data.serialized} alreadyInit=${initializedHandlesRef.current.has(handle)}` - ) - if (initializedHandlesRef.current.has(handle)) return + if (initializedHandlesRef.current.has(handle)) { + return + } const cols = (data.cols as number) || 80 const rows = (data.rows as number) || 24 + const scrollbackCols = cols + const scrollbackRows = rows const initialData = typeof data.serialized === 'string' && data.serialized.length > 0 ? data.serialized : '' - getTerminalRef(handle)?.init(cols, rows, initialData) + const ref = getTerminalRef(handle) + // Why: previously we set `initializedHandlesRef` even when the + // WebView wasn't mounted yet (ref=null). The init message went + // nowhere, but the flag stayed true, so any subsequent scrollback + // for THIS handle was silently dropped → blank terminal. Only + // mark initialized if init() actually reached the WebView. + if (!ref) { + console.log('[fit][session] scrollback DROPPED — no terminal ref', { + handle: handle.slice(-8), + cols, + rows + }) + return + } + ref.init(cols, rows, initialData) initializedHandlesRef.current.add(handle) if (data.displayMode) { setTerminalModes((prev) => new Map(prev).set(handle, data.displayMode as MobileDisplayMode) ) } - // Why: cold-start fit-to-screen guard. The first init() runs - // before xterm's DOM/canvas has fully laid out, so the - // applyFitScale that init queues internally can land while - // term.element.scrollWidth is still stale or zero — leaving - // the terminal un-zoomed until the user toggles the resize - // button. Re-fire resetZoom after a short delay so it runs - // against a settled DOM. Mirrors the 'resized' handler below. + // Why: belt-and-suspenders cold-start fit. The applyFitScale + // queued by init() runs after writes drain, but on cold start + // xterm's scrollWidth can still be transient when it commits. + // Re-fire after a short delay so it runs against a settled DOM. + // Mirrors the 'resized' handler below. setTimeout(() => getTerminalRef(handle)?.resetZoom(), 200) // Why: viewport measurement needs xterm to be initialized (cell // dimensions come from the renderer). On the first subscribe the // WebView hasn't loaded yet, so viewportRef is null and the server // can't auto-fit. After the first init we can measure, then // resubscribe so the server gets the viewport and phone-fits. - if (!viewportMeasuredRef.current) { + // If viewport was measured by a parallel path BUT the scrollback + // we just received came back at desktop dims, our subscribe + // beat the measure; the server still has a null viewport for + // this subscriber record — resubscribe so it gets stored. + const needsResubscribe = + !viewportMeasuredRef.current || + (viewportRef.current != null && + (scrollbackCols !== viewportRef.current.cols || + scrollbackRows !== viewportRef.current.rows)) + if (needsResubscribe) { void (async () => { + // Why: wait for the WebView's init() rAF chain to fully + // run (term.open → renderService population → first + // paint) before measuring. Without this, the measure + // postMessage races ahead of init's async work and + // returns null (term not ready / cells size 0), the + // resubscribe never fires, and the server never gets + // phone dims. See log dump 2026-05-06 confirming the + // race + measure-result null pattern. + await getTerminalRef(handle)?.awaitReady() + if (subscribeSeqRef.current.get(handle) !== seq) return const dims = await getTerminalRef(handle)?.measureFitDimensions( terminalFrameHeightRef.current || undefined ) - if (dims && !viewportMeasuredRef.current) { + // Why: re-check seq after the awaits — awaitReady (up to + // 3s) and measureFitDimensions can take hundreds of ms, + // during which a newer subscribe cycle may have armed + // its own subscription. Tearing it down here would reset + // the freshly-armed initialized flag and re-subscribe a + // stale generation. + if (subscribeSeqRef.current.get(handle) !== seq) return + if (!getTerminalRef(handle)) return + // Why: we just got `scrollback` with cols=80 (server's + // default fallback for null viewport). That means the + // server-side subscriber record was registered before we + // could send viewport. Even if `viewportMeasuredRef` + // raced ahead via a parallel `measureViewportOnce`, the + // server still has a null viewport for THIS subscriber + // record — we MUST resubscribe so the server stores it. + if (dims) { viewportRef.current = dims viewportMeasuredRef.current = true unsubscribeTerminal(handle) @@ -288,11 +412,28 @@ export default function SessionScreen() { })() } } else if (data.type === 'data') { - getTerminalRef(handle)?.write(data.chunk as string) + // Why: log when data arrives but the WebView ref is missing + // — this is the most likely cause of "blank but input works": + // server stream is alive, sends flow, but writes are dropped + // because the WebView ref disappeared (unmount mid-flight) or + // the scrollback never landed (so xterm has no buffer). + const dataRef = getTerminalRef(handle) + if (!dataRef) { + console.log('[fit][session] data DROPPED — no terminal ref', { + handle: handle.slice(-8), + chunkLen: typeof data.chunk === 'string' ? data.chunk.length : 0, + initialized: initializedHandlesRef.current.has(handle) + }) + return + } + if (!initializedHandlesRef.current.has(handle)) { + console.log('[fit][session] data RECEIVED before scrollback', { + handle: handle.slice(-8), + chunkLen: typeof data.chunk === 'string' ? data.chunk.length : 0 + }) + } + dataRef.write(data.chunk as string) } else if (data.type === 'resized') { - console.log( - `[mobile-fit] resized handle=${handle} cols=${data.cols} rows=${data.rows} displayMode=${data.displayMode} reason=${(data as Record).reason}` - ) // Why: inline resize event — the server changed the PTY dimensions // (mode toggle or desktop restore). Reinitialize xterm at the new // dims with fresh scrollback. No resubscribe needed. @@ -332,17 +473,24 @@ export default function SessionScreen() { if (!client) return if (toggleInFlightRef.current.has(handle)) return const current = terminalModes.get(handle) ?? 'auto' - const next: MobileDisplayMode = current === 'auto' || current === 'phone' ? 'desktop' : 'auto' + // Why: 'phone' on the wire is an observation ("currently phone-fitted"), + // not a setting. The toggle only ever requests 'auto' or 'desktop'. + const next: 'auto' | 'desktop' = + current === 'auto' || current === 'phone' ? 'desktop' : 'auto' toggleInFlightRef.current.add(handle) try { await client.sendRequest('terminal.setDisplayMode', { terminal: handle, mode: next, - // Why: presence-lock take-floor signal. Sending mode=auto/phone - // is a deliberate "I want to drive at phone dims" gesture. + // Why: presence-lock take-floor signal — requesting 'auto' is the + // explicit "I want to drive at phone dims" gesture. ...(deviceTokenRef.current ? { client: { id: deviceTokenRef.current, type: 'mobile' as const } } - : {}) + : {}), + // Why: late-bind viewport for terminals whose subscribe record + // was registered before measurement landed. Without this the + // server's stored viewport is null and auto toggles no-op. + ...(viewportRef.current && next === 'auto' ? { viewport: viewportRef.current } : {}) }) } catch { // Mode change failed — server state unchanged, UI stays in sync. @@ -366,9 +514,6 @@ export default function SessionScreen() { }) if (response.ok) { const result = (response as RpcSuccess).result as { terminals: Terminal[] } - console.log( - `[mobile-fit] fetchTerminals count=${result.terminals.length} allowEmpty=${allowEmptyLoaded} activeHandle=${activeHandleRef.current} lastKnown=${lastKnownTerminalCountRef.current}` - ) if (result.terminals.length === 0 && !allowEmptyLoaded) { return @@ -380,9 +525,6 @@ export default function SessionScreen() { // rapid interactions while still allowing genuine cleanup. if (result.terminals.length === 0 && lastKnownTerminalCountRef.current > 0) { lastKnownTerminalCountRef.current = 0 - console.log( - `[mobile-fit] fetchTerminals SKIP first empty — will clear on next fetch if still empty` - ) return } @@ -397,7 +539,19 @@ export default function SessionScreen() { lastKnownTerminalCountRef.current = result.terminals.length const current = activeHandleRef.current - setTerminals(result.terminals) + // Why: defense-in-depth dedupe. If the server ever returns a list + // with the same handle twice (race during rename/split, or stale + // process tracking), React would throw 'two children with same + // key' on render. Keep the first occurrence — list order matters + // for the tab strip, and createParams puts new tabs at the end. + const seen = new Set() + const deduped = result.terminals.filter((t) => { + if (seen.has(t.handle)) return false + seen.add(t.handle) + return true + }) + + setTerminals(deduped) setTerminalsLoaded(true) if (!current || !result.terminals.some((t) => t.handle === current)) { @@ -419,34 +573,40 @@ export default function SessionScreen() { [client, worktreeId, subscribeToTerminal, unsubscribeTerminal] ) + // Why: keep clientRef in sync with the shared client from + // useHostClient() so the existing imperative call sites + // (clientRef.current.sendRequest...) keep working without churn. useEffect(() => { - let disposed = false - let rpcClient: RpcClient | null = null - - void (async () => { - const hosts = await loadHosts() - const host = hosts.find((h) => h.id === hostId) - if (!host || disposed) return - - deviceTokenRef.current = host.deviceToken - rpcClient = connect(host.endpoint, host.deviceToken, host.publicKeyB64, setConnState) - if (disposed) { - rpcClient.close() - return - } - setClient(rpcClient) - clientRef.current = rpcClient - })() + clientRef.current = client + }, [client]) + // Why: only clear terminal cache on actual unmount. Running it whenever + // `client` changes — including the initial null → real-client transition + // from useHostClient's async open path — would unsubscribe terminals and + // wipe xterm state mid-subscribe on a normal session-screen mount. + useEffect(() => { return () => { - disposed = true clearTerminalCache() - rpcClient?.close() - if (clientRef.current === rpcClient) { - clientRef.current = null - } } - }, [clearTerminalCache, hostId]) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + // Why: deviceToken is read from host record so feature code can pass + // `client.id` on subscribe/send for driver-state-machine identity. + // The shared client itself stays alive across screens; we just need + // the token alongside the client. + useEffect(() => { + if (!hostId) return + let stale = false + void loadHosts().then((hosts) => { + if (stale) return + const host = hosts.find((h) => h.id === hostId) + if (host) deviceTokenRef.current = host.deviceToken + }) + return () => { + stale = true + } + }, [hostId]) useEffect(() => { void loadCustomKeys().then(setCustomKeys) @@ -456,71 +616,88 @@ export default function SessionScreen() { // doesn't resize the window) and refit xterm once the layout settles so the // terminal grid matches the new visible area. iOS exposes 'will' events that // animate in sync with the IME; Android only fires 'did' events reliably. - useEffect(() => { - let refitTimer: ReturnType | null = null - const scheduleRefit = () => { - if (refitTimer) clearTimeout(refitTimer) - refitTimer = setTimeout(() => { - const handle = activeHandleRef.current - if (!handle) return - const ref = terminalRefs.current.get(handle) - if (!ref) return - void (async () => { - const dims = await ref.measureFitDimensions(terminalFrameHeightRef.current || undefined) - if (!dims) return - const prev = viewportRef.current - if (prev && prev.cols === dims.cols && prev.rows === dims.rows) return - viewportRef.current = dims - viewportMeasuredRef.current = true - // Why: prefer the in-place viewport update RPC over the legacy - // unsubscribe → subscribe cycle. This keeps the server-side - // mobile subscriber record alive (no driver=idle blip on the - // desktop banner; no false phone-fit baseline capture on the - // re-subscribe). The 'resized' event from the server reinits - // the xterm at the new dims via the existing subscription - // stream. Falls back to the unsubscribe/subscribe path if the - // RPC isn't available (older host build) or no client is - // connected. See docs/mobile-presence-lock.md. - const rpc = clientRef.current - const deviceToken = deviceTokenRef.current - if (rpc && deviceToken) { - try { - const response = await rpc.sendRequest('terminal.updateViewport', { - terminal: handle, - client: { id: deviceToken, type: 'mobile' as const }, - viewport: dims - }) - if (response.ok) { - return - } - } catch { - // Fall through to legacy resubscribe. - } + // Also drives re-measurement when other layout-affecting state changes + // (e.g. tab strip toggling visibility when the terminal count crosses + // 0↔1 — without this, a freshly-created 2nd tab subscribes with a + // stale viewport that doesn't account for the now-visible tab strip, + // and the server phone-fits to dims a few rows too tall). + const refitTimerRef = useRef | null>(null) + const scheduleViewportRefit = useCallback(() => { + if (refitTimerRef.current) clearTimeout(refitTimerRef.current) + refitTimerRef.current = setTimeout(() => { + const handle = activeHandleRef.current + if (!handle) return + const ref = terminalRefs.current.get(handle) + if (!ref) return + void (async () => { + const dims = await ref.measureFitDimensions(terminalFrameHeightRef.current || undefined) + if (!dims) return + const prev = viewportRef.current + if (prev && prev.cols === dims.cols && prev.rows === dims.rows) return + viewportRef.current = dims + viewportMeasuredRef.current = true + // Why: prefer the in-place viewport update RPC over the legacy + // unsubscribe → subscribe cycle. This keeps the server-side + // mobile subscriber record alive (no driver=idle blip on the + // desktop banner; no false phone-fit baseline capture on the + // re-subscribe). See docs/mobile-presence-lock.md. + const rpc = clientRef.current + const deviceToken = deviceTokenRef.current + if (rpc && deviceToken) { + try { + const response = await rpc.sendRequest('terminal.updateViewport', { + terminal: handle, + client: { id: deviceToken, type: 'mobile' as const }, + viewport: dims + }) + if (response.ok) return + } catch { + // Fall through to legacy resubscribe. } - unsubscribeTerminal(handle) - initializedHandlesRef.current.delete(handle) - subscribeToTerminal(handle) - })() - }, 150) - } + } + unsubscribeTerminal(handle) + initializedHandlesRef.current.delete(handle) + subscribeToTerminal(handle) + })() + }, 150) + }, [subscribeToTerminal, unsubscribeTerminal]) + + useEffect(() => { const onShow = (e: KeyboardEvent) => { setKeyboardHeight(e.endCoordinates?.height ?? 0) - scheduleRefit() + scheduleViewportRefit() } const onHide = () => { setKeyboardHeight(0) - scheduleRefit() + scheduleViewportRefit() } const showEvent = Platform.OS === 'ios' ? 'keyboardWillShow' : 'keyboardDidShow' const hideEvent = Platform.OS === 'ios' ? 'keyboardWillHide' : 'keyboardDidHide' const showSub = Keyboard.addListener(showEvent, onShow) const hideSub = Keyboard.addListener(hideEvent, onHide) return () => { - if (refitTimer) clearTimeout(refitTimer) + if (refitTimerRef.current) clearTimeout(refitTimerRef.current) showSub.remove() hideSub.remove() } - }, [subscribeToTerminal, unsubscribeTerminal]) + }, [scheduleViewportRefit]) + + // Why: the tab strip is hidden when only one terminal exists and shown + // once a second is created. Crossing the 1↔2 boundary changes the + // visible terminal area by ~40px, so the cached viewport dims in + // viewportRef become stale. Mark the viewport as un-measured so the + // next subscribe path's self-correcting loop (init → measure → + // resubscribe-with-fresh-viewport, see the !viewportMeasuredRef branch + // above) re-runs against the new layout. Also schedule an explicit + // refit to cover the case where no new subscribe is happening. + const tabStripVisible = terminals.length > 1 + const prevTabStripVisibleRef = useRef(tabStripVisible) + useEffect(() => { + if (prevTabStripVisibleRef.current === tabStripVisible) return + prevTabStripVisibleRef.current = tabStripVisible + viewportMeasuredRef.current = false + scheduleViewportRefit() + }, [tabStripVisible, scheduleViewportRefit]) useEffect(() => { if (hostId && worktreeId) { @@ -612,9 +789,6 @@ export default function SessionScreen() { const switchTab = useCallback( (handle: string) => { const prev = activeHandleRef.current - console.log( - `[mobile-fit] switchTab prev=${prev} next=${handle} hasUnsub=${terminalUnsubsRef.current.has(handle)} hasRef=${!!terminalRefs.current.get(handle)}` - ) activeHandleRef.current = handle setActiveHandle(handle) if (prev && prev !== handle) { @@ -641,9 +815,6 @@ export default function SessionScreen() { const setTerminalWebViewRef = useCallback((handle: string, ref: TerminalWebViewHandle | null) => { if (ref) { terminalRefs.current.set(handle, ref) - console.log( - `[mobile-fit] setTerminalWebViewRef handle=${handle} isActive=${handle === activeHandleRef.current} webReady=${webReadyHandlesRef.current.has(handle)}` - ) } else { terminalRefs.current.delete(handle) } @@ -653,15 +824,13 @@ export default function SessionScreen() { (handle: string) => { const wasAlreadyReady = webReadyHandlesRef.current.has(handle) webReadyHandlesRef.current.add(handle) - console.log( - `[mobile-fit] handleTerminalWebReady handle=${handle} isActive=${handle === activeHandleRef.current} wasAlreadyReady=${wasAlreadyReady} wasInitialized=${initializedHandlesRef.current.has(handle)}` - ) if (wasAlreadyReady && initializedHandlesRef.current.has(handle)) { // Why: the native WebView reloaded (Metro hot reload or Android // process churn). The old xterm buffer is gone, so force a fresh // scrollback snapshot. Only resubscribe if this is a reload — on // first load the subscription is already running and pendingMessages // will flush the queued init after this callback returns. + // (unsubscribeTerminal also clears layoutSeqRef for this handle.) unsubscribeTerminal(handle) initializedHandlesRef.current.delete(handle) if (handle === activeHandleRef.current) { @@ -672,10 +841,18 @@ export default function SessionScreen() { // Why: on first web-ready, the initial subscribeToTerminal call from // fetchTerminals may have been skipped (reason=no-ref, WebView wasn't // mounted yet). Now that the WebView is ready, subscribe if this is the - // active terminal and no subscription is running. + // active terminal and no subscription is running. Await measure before + // subscribe so the very first subscribe carries the viewport — without + // this, subscribe(viewport=null) lands on the server first and the + // post-scrollback measure path's resubscribe sees alreadyMeasured=true + // (because measureViewportOnce won the race) and silently skips. if (handle === activeHandleRef.current && !terminalUnsubsRef.current.has(handle)) { - void measureViewportOnce(handle) - subscribeToTerminal(handle) + void (async () => { + await measureViewportOnce(handle) + if (handle === activeHandleRef.current && !terminalUnsubsRef.current.has(handle)) { + subscribeToTerminal(handle) + } + })() } }, [measureViewportOnce, subscribeToTerminal, unsubscribeTerminal] @@ -724,6 +901,187 @@ export default function SessionScreen() { } } + // Why: press-and-hold key repeat for keys flagged repeatable (arrows, + // backspace, forward-delete). Matches iOS keyboard cadence: instant first + // fire, then ~400ms before the second, then ~45ms between subsequent + // repeats. Non-repeatable keys (Tab, Esc, Ctrl-*) intentionally fire once + // because holding them is destructive or meaningless. + const repeatTimeoutRef = useRef | null>(null) + const repeatIntervalRef = useRef | null>(null) + // Why: hold the latest handleAccessoryKey in a ref so the repeat interval + // always invokes the current callback. Otherwise a held key keeps firing + // through the callback captured when the interval started, which can route + // bytes to a stale terminal/RPC client after a tab switch or reconnect + // mid-hold. + const handleAccessoryKeyRef = useRef(handleAccessoryKey) + handleAccessoryKeyRef.current = handleAccessoryKey + const stopAccessoryRepeat = useCallback(() => { + if (repeatTimeoutRef.current) { + clearTimeout(repeatTimeoutRef.current) + repeatTimeoutRef.current = null + } + if (repeatIntervalRef.current) { + clearInterval(repeatIntervalRef.current) + repeatIntervalRef.current = null + } + }, []) + const startAccessoryRepeat = useCallback( + (bytes: string) => { + stopAccessoryRepeat() + repeatTimeoutRef.current = setTimeout(() => { + repeatIntervalRef.current = setInterval(() => { + void handleAccessoryKeyRef.current(bytes) + }, 45) + }, 400) + }, + [stopAccessoryRepeat] + ) + useEffect(() => { + return () => stopAccessoryRepeat() + }, [stopAccessoryRepeat]) + + const showToast = useCallback((message: string, durationMs = 1200) => { + setToastMessage(message) + Animated.timing(toastOpacityRef.current, { + toValue: 1, + duration: 150, + useNativeDriver: true + }).start(() => { + setTimeout(() => { + Animated.timing(toastOpacityRef.current, { + toValue: 0, + duration: 200, + useNativeDriver: true + }).start(() => setToastMessage(null)) + }, durationMs) + }) + }, []) + + const handleSelectionMode = useCallback((handle: string, active: boolean) => { + if (handle !== activeHandleRef.current) return + setSelectModeActive(active) + if (active) Keyboard.dismiss() + }, []) + + const handleSelectionCopy = useCallback( + async (handle: string, text: string) => { + if (handle !== activeHandleRef.current) return + if (!text || text.length === 0) { + terminalRefs.current.get(handle)?.cancelSelect() + return + } + try { + await Clipboard.setStringAsync(text) + triggerSuccess() + // Why: Android 13+ shows its own system "Copied to clipboard" toast on + // every clipboard write, so our toast would be redundant; iOS shows + // nothing on copy (it only banners on paste), so the in-app toast is + // the only success signal there. + if (Platform.OS === 'ios') showToast('Copied') + terminalRefs.current.get(handle)?.cancelSelect() + } catch (e) { + triggerError() + const err = e as { name?: string; message?: string } + // eslint-disable-next-line no-console + console.warn('[mobile-clip] setString failed', { + name: err.name, + message: err.message + }) + showToast("Couldn't copy", 1500) + } + }, + [showToast] + ) + + const handleSelectionEvicted = useCallback( + (handle: string) => { + if (handle !== activeHandleRef.current) return + // eslint-disable-next-line no-console + console.warn('[mobile-clip] selection evicted') + showToast('Selection cleared (scrolled out of buffer)', 1500) + setSelectModeActive(false) + }, + [showToast] + ) + + const handleModesChanged = useCallback((handle: string, modes: TerminalModes) => { + ptyModesRef.current.set(handle, modes) + initialModesSeenRef.current.add(handle) + }, []) + + const handleHaptic = useCallback((kind: 'selection' | 'success' | 'error' | 'edge-bump') => { + if (kind === 'selection') triggerSelection() + else if (kind === 'success') triggerSuccess() + else if (kind === 'error') triggerError() + else if (kind === 'edge-bump') triggerEdgeBump() + }, []) + + const handlePaste = useCallback(async () => { + if (!client || !activeHandle || !canSend) return + try { + const text = await Clipboard.getStringAsync() + if (text.length === 0) return + const modes = ptyModesRef.current.get(activeHandle) || { + bracketedPasteMode: false, + altScreen: false + } + const wrap = modes.bracketedPasteMode && !modes.altScreen + // Why: strip embedded bracketed-paste markers from clipboard text so a + // malicious copy containing `\x1b[201~` can't terminate paste mode early + // and have the trailing bytes interpreted as shell commands. Matches + // xterm.js / iTerm2 behavior. + // eslint-disable-next-line no-control-regex -- intentional bracketed-paste marker stripping + const sanitized = wrap ? text.replace(/\x1b\[20[01]~/g, '') : text + const payload = wrap ? `\x1b[200~${sanitized}\x1b[201~` : sanitized + const wrappedBytes = new TextEncoder().encode(payload).byteLength + if (wrappedBytes > 256 * 1024) { + triggerError() + // eslint-disable-next-line no-console + console.warn('[mobile-clip] paste oversized', { wrappedBytes }) + showToast('Paste too large (max 256 KiB)', 1500) + return + } + await client.sendRequest('terminal.send', { + terminal: activeHandle, + text: payload, + enter: false, + ...(deviceTokenRef.current + ? { client: { id: deviceTokenRef.current, type: 'mobile' as const } } + : {}) + }) + triggerSelection() + void Clipboard.hasStringAsync().then(setCanPaste) + } catch (e) { + triggerError() + const err = e as { name?: string; message?: string } + const isDisconnected = connState !== 'connected' + // eslint-disable-next-line no-console + console.warn('[mobile-clip] paste failed', { name: err.name, message: err.message }) + if (isDisconnected) showToast('Paste failed (disconnected)', 1500) + } + }, [client, activeHandle, canSend, connState, showToast]) + + // Why: refresh canPaste on mount, AppState active, after paste. + useEffect(() => { + let mounted = true + const refresh = () => { + void Clipboard.hasStringAsync().then((has) => { + if (mounted) setCanPaste(has) + }) + } + refresh() + const sub = AppState.addEventListener('change', (s: AppStateStatus) => { + if (s === 'active') refresh() + else if (selectModeActive && activeHandleRef.current) { + terminalRefs.current.get(activeHandleRef.current)?.cancelSelect() + } + }) + return () => { + mounted = false + sub.remove() + } + }, [selectModeActive]) + async function handleCreateTerminal() { if (!client || creating) return @@ -747,10 +1105,17 @@ export default function SessionScreen() { } activeHandleRef.current = created.handle setActiveHandle(created.handle) - setTerminals((prev) => [ - ...prev, - { handle: created.handle, title: created.title || 'Terminal', isActive: true } - ]) + setTerminals((prev) => { + // Why: guard against duplicates if a parallel fetchTerminals() + // already inserted this handle. Without this, React throws + // 'two children with the same key' when both the optimistic + // insert and a canonical refetch race during creation. + if (prev.some((t) => t.handle === created.handle)) return prev + return [ + ...prev, + { handle: created.handle, title: created.title || 'Terminal', isActive: true } + ] + }) subscribeToTerminal(created.handle) setTimeout(() => void fetchTerminals(), 500) } else { @@ -950,8 +1315,21 @@ export default function SessionScreen() { active={terminal.handle === activeHandle} onRef={setTerminalWebViewRef} onWebReady={handleTerminalWebReady} + onSelectionMode={handleSelectionMode} + onSelectionCopy={handleSelectionCopy} + onSelectionEvicted={handleSelectionEvicted} + onModesChanged={handleModesChanged} + onHaptic={handleHaptic} /> ))} + {toastMessage && ( + + {toastMessage} + + )} )} @@ -987,6 +1365,24 @@ export default function SessionScreen() { )} + {canPaste && ( + [ + styles.accessoryKey, + pressed && styles.accessoryKeyPressed, + !canSend && styles.accessoryKeyDisabled + ]} + disabled={!canSend} + onPress={() => void handlePaste()} + accessibilityLabel="Paste from clipboard" + > + + Paste + + + )} {ACCESSORY_KEYS.map((key) => ( void handleAccessoryKey(key.bytes)} + onPressIn={() => { + if (!key.repeatable) return + void handleAccessoryKey(key.bytes) + startAccessoryRepeat(key.bytes) + }} + onPressOut={() => { + if (key.repeatable) stopAccessoryRepeat() + }} + onPress={() => { + if (key.repeatable) return + void handleAccessoryKey(key.bytes) + }} accessibilityLabel={key.accessibilityLabel ?? `Send ${key.label}`} > + ) diff --git a/mobile/app/index.tsx b/mobile/app/index.tsx index 7f01cecb679..1981d24db28 100644 --- a/mobile/app/index.tsx +++ b/mobile/app/index.tsx @@ -11,19 +11,38 @@ import { GitPullRequest, ChevronRight, Terminal, - Plus + Plus, + RefreshCw, + PowerOff, + Edit3 } from 'lucide-react-native' +import { ClaudeIcon, OpenAIIcon } from '../src/components/AgentIcons' +import { + type AccountsSnapshot, + type ProviderKey, + getActiveProviderRateLimits, + UsageBar +} from '../src/components/AccountUsage' import AsyncStorage from '@react-native-async-storage/async-storage' import { loadHosts, removeHost, renameHost } from '../src/transport/host-store' -import { connect, type RpcClient } from '../src/transport/rpc-client' +import type { RpcClient } from '../src/transport/rpc-client' +import { + useAllHostClients, + useCloseHost, + useForceReconnect, + usePrimeHosts +} from '../src/transport/client-context' +import { classifyConnection } from '../src/transport/connection-health' import { subscribeToDesktopNotifications } from '../src/notifications/mobile-notifications' import type { ConnectionState, HostProfile } from '../src/transport/types' import { triggerMediumImpact } from '../src/platform/haptics' import { OrcaLogo } from '../src/components/OrcaLogo' +import { StatusDot } from '../src/components/StatusDot' import { TextInputModal } from '../src/components/TextInputModal' -import { ActionSheetModal } from '../src/components/ActionSheetModal' +import { ActionSheetModal, type ActionSheetAction } from '../src/components/ActionSheetModal' import { ConfirmModal } from '../src/components/ConfirmModal' import { setCachedWorktrees, getCachedWorktrees } from '../src/cache/worktree-cache' +import { loadHomeSnapshot, saveHomeSnapshot } from '../src/cache/home-snapshot-cache' import { colors, spacing, radii } from '../src/theme/mobile-theme' function endpointLabel(endpoint: string): string { @@ -35,15 +54,6 @@ function endpointLabel(endpoint: string): string { } } -const STATUS_LABELS: Record = { - connected: 'Connected', - connecting: 'Connecting…', - disconnected: 'Disconnected', - reconnecting: 'Reconnecting…', - handshaking: 'Connecting…', - 'auth-failed': 'Auth failed' -} - type StatsSummary = { totalAgentsSpawned: number totalPRsCreated: number @@ -78,6 +88,21 @@ function formatDuration(ms: number): string { return `${totalMinutes}m` } +// Why: derive a stable per-instance identity for RpcClient so the wireUp +// effect's dep key changes when forceReconnect swaps the underlying client +// for a host (without this, listeners stay attached to the closed client +// and notifications/accounts subs never re-attach). +const clientIdentities = new WeakMap() +let nextClientIdentity = 1 +function clientKey(client: RpcClient): number { + let id = clientIdentities.get(client) + if (id == null) { + id = nextClientIdentity++ + clientIdentities.set(client, id) + } + return id +} + function fetchStats( client: RpcClient, setStats: (s: StatsSummary) => void, @@ -102,16 +127,24 @@ function fetchWorktreeInfo( ) => void, disposed: () => boolean ) { - const markLoaded = () => { - setInfo((prev) => ({ - ...prev, - [hostId]: { - hostId, - totalWorktrees: 0, - activeCount: 0, - lastActiveWorktree: null + // Why: only seed an empty zeroed entry when this host has no prior info + // at all (e.g., first ever load before any cache hydration). On a + // transient failure for a host that already has cached data, leave the + // cached entry alone so the Resume card and host-meta line don't + // momentarily flip to "0 worktrees" / disappear during reconnects. + const markLoadedIfMissing = () => { + setInfo((prev) => { + if (prev[hostId]) return prev + return { + ...prev, + [hostId]: { + hostId, + totalWorktrees: 0, + activeCount: 0, + lastActiveWorktree: null + } } - })) + }) } client @@ -135,14 +168,34 @@ function fetchWorktreeInfo( } })) } else { - markLoaded() + markLoadedIfMissing() } }) .catch(() => { - if (!disposed()) markLoaded() + if (!disposed()) markLoadedIfMissing() }) } +function fetchAccountsSnapshot( + client: RpcClient, + hostId: string, + setSnapshots: ( + updater: (prev: Record) => Record + ) => void, + disposed: () => boolean +) { + client + .sendRequest('accounts.list') + .then((response) => { + if (disposed()) return + if (response.ok) { + const snapshot = response.result as AccountsSnapshot + setSnapshots((prev) => ({ ...prev, [hostId]: snapshot })) + } + }) + .catch(() => {}) +} + // Why: repo names get a stable color derived from hashing, matching the // host detail page's colored dots for visual consistency. const REPO_COLORS = ['#8b5cf6', '#3b82f6', '#22c55e', '#f59e0b', '#ef4444', '#ec4899', '#06b6d4'] @@ -162,12 +215,78 @@ export default function HomeScreen() { const [renameTarget, setRenameTarget] = useState(null) const [confirmRemove, setConfirmRemove] = useState(null) const [hostStates, setHostStates] = useState>({}) + const [hostAttempts, setHostAttempts] = useState>({}) + const [hostLastConnected, setHostLastConnected] = useState>({}) const [stats, setStats] = useState(null) const [worktreeInfo, setWorktreeInfo] = useState>({}) + const [accountsByHost, setAccountsByHost] = useState>({}) const [lastVisited, setLastVisited] = useState<{ hostId: string; worktreeId: string } | null>( null ) - const clientsRef = useRef>([]) + + // Why: read shared clients from the per-host store. Replaces the prior + // pattern of opening N independent WebSockets here. See + // docs/mobile-shared-client-per-host.md. + const hostIds = useMemo(() => hosts.map((h) => h.id), [hosts]) + const allClients = useAllHostClients(hostIds) + const closeHostClient = useCloseHost() + const forceReconnectHost = useForceReconnect() + const primeHosts = usePrimeHosts() + // Why: feed the loaded HostProfiles into the provider's prime cache as + // soon as we have them. This avoids a second Keychain pass inside + // openEntry on cold start (which serialised behind the first one and + // showed up as multi-second connect latency). + useEffect(() => { + if (hosts.length > 0) primeHosts(hosts) + }, [hosts, primeHosts]) + const allClientsRef = useRef>([]) + useEffect(() => { + allClientsRef.current = allClients.map((entry) => ({ + hostId: entry.hostId, + client: entry.client + })) + }, [allClients]) + + // Why: hydrate the home page from a persisted snapshot on cold-start so + // Resume + Account-usage cards paint immediately with last-known data + // instead of flashing empty for ~1s while the WebSocket reconnects. + // Stream/list responses overwrite this seed in place when they arrive. + const hydratedRef = useRef(false) + useEffect(() => { + if (hydratedRef.current) return + hydratedRef.current = true + let cancelled = false + void loadHomeSnapshot().then((snap) => { + if (cancelled || !snap) return + setWorktreeInfo((prev) => (Object.keys(prev).length > 0 ? prev : snap.worktreeInfo)) + setAccountsByHost((prev) => (Object.keys(prev).length > 0 ? prev : snap.accountsByHost)) + for (const [hostId, info] of Object.entries(snap.worktreeInfo)) { + const wt = info.lastActiveWorktree + if (wt) { + // Why: also seed the in-memory worktree cache so resumeWorktree's + // lastVisited fast-path can find the cached worktree object. + setCachedWorktrees(hostId, [wt]) + } + } + }) + return () => { + cancelled = true + } + }, []) + + // Why: persist the merged snapshot whenever either piece updates so the + // next cold-start has fresh seed data. The cache module debounces writes + // internally so a flurry of streamed updates doesn't hammer disk. + useEffect(() => { + if (Object.keys(worktreeInfo).length === 0 && Object.keys(accountsByHost).length === 0) { + return + } + saveHomeSnapshot({ + worktreeInfo, + accountsByHost, + savedAt: Date.now() + }) + }, [worktreeInfo, accountsByHost]) useFocusEffect( useCallback(() => { @@ -181,10 +300,11 @@ export default function HomeScreen() { setLastVisited(JSON.parse(raw)) } catch {} }) - for (const entry of clientsRef.current) { + for (const entry of allClientsRef.current) { if (entry.client.getState() === 'connected') { fetchStats(entry.client, setStats, () => stale) fetchWorktreeInfo(entry.client, entry.hostId, setWorktreeInfo, () => stale) + fetchAccountsSnapshot(entry.client, entry.hostId, setAccountsByHost, () => stale) } } return () => { @@ -198,68 +318,156 @@ export default function HomeScreen() { [hosts] ) + // Why: mirror per-host connection state into hostStates so existing + // render code (status dots, connecting indicators) keeps working. useEffect(() => { - let disposed = false - const notifCleanups: Array<() => void> = [] - const entries = hosts.flatMap((host) => { - if (!host.publicKeyB64 || !host.deviceToken) { - setHostStates((prev) => ({ ...prev, [host.id]: 'auth-failed' })) - return [] + setHostAttempts((prev) => { + const next: Record = { ...prev } + let changed = false + for (const entry of allClients) { + const a = entry.client.getReconnectAttempt() + if (next[entry.hostId] !== a) { + next[entry.hostId] = a + changed = true + } } - setHostStates((prev) => ({ - ...prev, - [host.id]: prev[host.id] ?? 'connecting' - })) - let client: ReturnType - try { - client = connect(host.endpoint, host.deviceToken, host.publicKeyB64, (state) => { - if (disposed) return - setHostStates((prev) => ({ ...prev, [host.id]: state })) - }) - } catch { - setHostStates((prev) => ({ ...prev, [host.id]: 'auth-failed' })) - return [] + return changed ? next : prev + }) + setHostLastConnected((prev) => { + const next: Record = { ...prev } + let changed = false + for (const entry of allClients) { + const t = entry.client.getLastConnectedAt() + if (next[entry.hostId] !== t) { + next[entry.hostId] = t + changed = true + } } + return changed ? next : prev + }) + setHostStates((prev) => { + const next: Record = { ...prev } + let changed = false + const liveIds = new Set(allClients.map((e) => e.hostId)) + for (const entry of allClients) { + if (next[entry.hostId] !== entry.state) { + next[entry.hostId] = entry.state + changed = true + } + } + // Why: when a paired host disappears from allClients (because the + // user tapped Disconnect, or the host record was invalid) the card + // must reflect that. We only force-update hosts whose state was + // already tracked — otherwise the initial-acquire frame (entry not + // yet materialised) would briefly flip every host to 'disconnected'. + for (const host of hosts) { + if (liveIds.has(host.id)) continue + if (!host.publicKeyB64 || !host.deviceToken) { + if (next[host.id] !== 'auth-failed') { + next[host.id] = 'auth-failed' + changed = true + } + continue + } + const prevState = next[host.id] + if (prevState && prevState !== 'disconnected' && prevState !== 'auth-failed') { + next[host.id] = 'disconnected' + changed = true + } + } + // Drop entries for hosts we no longer track at all. + for (const id of Object.keys(next)) { + if (!liveIds.has(id) && hosts.some((h) => h.id === id) === false) { + delete next[id] + changed = true + } + } + return changed ? next : prev + }) + }, [allClients, hosts]) + // Why: per-host streaming subscriptions (notifications + accounts) and + // one-shot stats fetches when each host transitions to 'connected'. + // Runs once per (hostId, client) pair and tears down when that pair + // changes. The provider keeps the underlying socket open across + // resubscription cycles so this is cheap. + useEffect(() => { + const cleanups: Array<() => void> = [] + for (const entry of allClients) { let unsubNotif: (() => void) | null = null + let unsubAccounts: (() => void) | null = null let statsFetched = false - const unsubState = client.onStateChange((state) => { + const wireUp = (state: ConnectionState) => { if (state === 'connected') { if (!unsubNotif) { - unsubNotif = subscribeToDesktopNotifications(client) + unsubNotif = subscribeToDesktopNotifications(entry.client) + } + if (!unsubAccounts) { + unsubAccounts = entry.client.subscribe('accounts.subscribe', null, (payload) => { + if (!payload || typeof payload !== 'object') return + const evt = payload as { type?: string; snapshot?: AccountsSnapshot } + if ((evt.type === 'ready' || evt.type === 'snapshot') && evt.snapshot) { + setAccountsByHost((prev) => ({ ...prev, [entry.hostId]: evt.snapshot! })) + } + }) } if (!statsFetched) { statsFetched = true - fetchStats(client, setStats, () => disposed) - fetchWorktreeInfo(client, host.id, setWorktreeInfo, () => disposed) + fetchStats(entry.client, setStats, () => false) + fetchWorktreeInfo(entry.client, entry.hostId, setWorktreeInfo, () => false) + } + } else { + if (unsubNotif) { + unsubNotif() + unsubNotif = null + } + if (unsubAccounts) { + unsubAccounts() + unsubAccounts = null } - } else if (unsubNotif) { - unsubNotif() - unsubNotif = null } - }) - notifCleanups.push(() => { + } + wireUp(entry.state) + const unsubState = entry.client.onStateChange(wireUp) + cleanups.push(() => { unsubState() unsubNotif?.() + unsubAccounts?.() }) - - return [{ hostId: host.id, client }] - }) - - clientsRef.current = entries - - return () => { - disposed = true - clientsRef.current = [] - for (const cleanup of notifCleanups) cleanup() - for (const entry of entries) entry.client.close() } - }, [hosts]) + return () => { + for (const c of cleanups) c() + } + // Why: depend on the host-id set AND each entry's client identity, so + // resubscriptions don't fire on every render that produces a new + // array reference, but DO fire when forceReconnect swaps the + // underlying client for a host (otherwise wireUp would keep firing + // on a closed client and never re-attach to the fresh one, leaving + // notifications/accounts subs broken until the user navigates). + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ + allClients + .map((e) => `${e.hostId}:${clientKey(e.client)}`) + .sort() + .join(',') + ]) // Why: prefer the worktree the user last opened on this device so the // "Resume" card reflects their mobile session history, not just the // desktop's most-recently-outputting worktree. + // Why: rendering used to be gated on hostStates === 'connected', which + // caused the Resume card to vanish for ~1s on every cold-start / + // resume-from-background while the WebSocket reconnected, even though we + // had perfectly good cached worktree data. Now the card stays visible as + // long as we have a cached lastActiveWorktree for any known host; the + // tap target is still the same and a fresher snapshot from the live RPC + // overwrites the card's contents in place when it lands. const resumeWorktree = useMemo(() => { + // Why: only surface Resume for hosts that are currently connected. + // Showing a stale cached worktree for a disconnected host is + // misleading — the user would tap into a session route that can't + // load anything until the host reconnects. Once the host reconnects, + // the card reappears with fresh data. if (lastVisited && hostStates[lastVisited.hostId] === 'connected') { const cached = getCachedWorktrees(lastVisited.hostId) as WorktreeSummary[] | null const match = cached?.find((w) => w.worktreeId === lastVisited.worktreeId) @@ -275,19 +483,21 @@ export default function HomeScreen() { return null }, [sortedHosts, hostStates, worktreeInfo, lastVisited]) - const resumeLoading = useMemo( - () => - sortedHosts.some((host) => { - const state = hostStates[host.id] ?? 'connecting' - return ( - state === 'connecting' || - state === 'handshaking' || - state === 'reconnecting' || - (state === 'connected' && !worktreeInfo[host.id]) - ) - }), - [sortedHosts, hostStates, worktreeInfo] - ) + // Why: only show the Account usage section for hosts that are currently + // connected. Showing stale cached usage for a disconnected host implies + // live data; better to hide until the host reconnects and we can refresh. + const accountsHosts = useMemo(() => { + const items: Array<{ host: HostProfile; snapshot: AccountsSnapshot }> = [] + for (const host of sortedHosts) { + if (hostStates[host.id] !== 'connected') continue + const snap = accountsByHost[host.id] + if (!snap) continue + const hasClaude = snap.claude.accounts.length > 0 + const hasCodex = snap.codex.accounts.length > 0 + if (hasClaude || hasCodex) items.push({ host, snapshot: snap }) + } + return items + }, [sortedHosts, hostStates, accountsByHost]) async function handleRename(newName: string) { if (!renameTarget) return @@ -303,6 +513,9 @@ export default function HomeScreen() { async function handleRemove() { if (!confirmRemove) return try { + // Why: close the shared client first so the WebSocket is gone + // before the host record disappears from loadHosts(). + closeHostClient(confirmRemove.id) await removeHost(confirmRemove.id) setConfirmRemove(null) setHosts(await loadHosts()) @@ -335,12 +548,12 @@ export default function HomeScreen() { Connect your desktop - Pair with Orca on your computer to monitor worktrees, watch agents work, and manage - terminals — all from your phone. + Pair with Orca on your computer to check on your agents, jump into any terminal, and + drive work from your phone. router.push('/pair-scan')}> - Scan Pairing Code + Pair Desktop @@ -408,8 +621,19 @@ export default function HomeScreen() { ItemSeparatorComponent={CardGap} renderItem={({ item }) => { const state = hostStates[item.id] ?? 'connecting' + const attempts = hostAttempts[item.id] ?? 0 + const lastConnectedAt = hostLastConnected[item.id] ?? null const connected = state === 'connected' const info = worktreeInfo[item.id] + const verdict = classifyConnection({ + state, + reconnectAttempts: attempts, + lastConnectedAt + }) + const isError = + verdict.kind === 'warning' || + verdict.kind === 'unreachable' || + verdict.kind === 'auth-failed' return ( [styles.hostCard, pressed && styles.hostCardPressed]} @@ -434,14 +658,9 @@ export default function HomeScreen() { {item.name} - - - {STATUS_LABELS[state]} + + + {verdict.label} {connected && info ? ` · ${info.totalWorktrees} worktree${info.totalWorktrees !== 1 ? 's' : ''}${info.activeCount > 0 ? ` · ${info.activeCount} active` : ''}` : ''} @@ -490,16 +709,84 @@ export default function HomeScreen() { - ) : hosts.length > 0 && resumeLoading ? ( + ) : null} + + {/* ─── Account usage ─── */} + {accountsHosts.length > 0 ? ( <> - Resume - - - - - - - + + Account usage + + {accountsHosts.map(({ host, snapshot }) => { + const claudeActiveId = snapshot.claude.activeAccountId + const claudeActive = + snapshot.claude.accounts.find((a) => a.id === claudeActiveId) ?? null + const codexActiveId = snapshot.codex.activeAccountId + const codexActive = + snapshot.codex.accounts.find((a) => a.id === codexActiveId) ?? null + const showHostName = accountsHosts.length > 1 + return ( + [ + styles.accountsCard, + pressed && styles.hostCardPressed + ]} + onPress={() => router.push(`/h/${host.id}/accounts`)} + > + {showHostName ? ( + + {host.name} + + ) : null} + {(['claude', 'codex'] as ProviderKey[]).map((provider) => { + const active = provider === 'claude' ? claudeActive : codexActive + const accounts = + provider === 'claude' + ? snapshot.claude.accounts + : snapshot.codex.accounts + if (accounts.length === 0) return null + const limits = getActiveProviderRateLimits(snapshot, provider) + const isFetching = + limits?.status === 'fetching' || limits?.status === 'idle' + const unavailable = + limits == null || + limits.status === 'unavailable' || + limits.status === 'error' + return ( + + + {provider === 'claude' ? ( + + ) : ( + + )} + + + + {active?.email ?? 'System default'} + + + + + + + + ) + })} + + ) + })} ) : null} @@ -511,7 +798,7 @@ export default function HomeScreen() { onPress={() => router.push('/pair-scan')} > - + Pair Desktop @@ -525,7 +812,7 @@ export default function HomeScreen() { }} > - + New Worktree @@ -540,25 +827,58 @@ export default function HomeScreen() { visible={actionTarget != null} title={actionTarget?.name} message={actionTarget ? endpointLabel(actionTarget.endpoint) : undefined} - actions={[ - { - label: 'Rename', + actions={(() => { + const host = actionTarget + if (!host) return [] + const state = hostStates[host.id] ?? 'connecting' + const isLive = + state === 'connected' || + state === 'connecting' || + state === 'handshaking' || + state === 'reconnecting' + // Why: "Reconnect" implies "you were connected, try again". If + // the client has never reached 'connected' this session (cold + // start, unreachable host, or after Disconnect) the action is + // functionally a fresh Connect — using the right verb makes + // the affordance match what tapping it actually does. + const hasEverConnected = (hostLastConnected[host.id] ?? null) != null + const items: ActionSheetAction[] = [] + items.push({ + label: hasEverConnected && isLive ? 'Reconnect' : 'Connect', + icon: RefreshCw, onPress: () => { - const host = actionTarget setActionTarget(null) - if (host) setRenameTarget(host) + void forceReconnectHost(host.id) } - }, - { + }) + if (isLive) { + items.push({ + label: 'Disconnect', + icon: PowerOff, + onPress: () => { + setActionTarget(null) + closeHostClient(host.id) + } + }) + } + items.push({ + label: 'Rename', + icon: Edit3, + onPress: () => { + setActionTarget(null) + setRenameTarget(host) + } + }) + items.push({ label: 'Remove', destructive: true, onPress: () => { - const host = actionTarget setActionTarget(null) - if (host) setConfirmRemove(host) + setConfirmRemove(host) } - } - ]} + }) + return items + })()} onClose={() => setActionTarget(null)} /> @@ -667,20 +987,21 @@ const styles = StyleSheet.create({ borderWidth: 1, borderColor: colors.borderSubtle, borderRadius: 10, - padding: spacing.md + paddingVertical: 10, + paddingHorizontal: spacing.md }, statIcon: { - width: 30, - height: 30, - borderRadius: 7, + width: 26, + height: 26, + borderRadius: 6, backgroundColor: 'rgba(255,255,255,0.04)', alignItems: 'center', justifyContent: 'center', - marginBottom: 10 + marginBottom: 6 }, statValue: { color: colors.textPrimary, - fontSize: 20, + fontSize: 18, fontWeight: '700', letterSpacing: -0.3 }, @@ -688,7 +1009,7 @@ const styles = StyleSheet.create({ color: colors.textMuted, fontSize: 11, fontWeight: '500', - marginTop: 3 + marginTop: 2 }, /* ─── Section heading ─── */ @@ -817,16 +1138,51 @@ const styles = StyleSheet.create({ flex: 1 }, - /* ─── Skeleton ─── */ - skeletonBlock: { - backgroundColor: colors.bgRaised, - opacity: 0.5 + /* ─── Account usage ─── */ + accountsCard: { + backgroundColor: colors.bgPanel, + borderWidth: 1, + borderColor: colors.borderSubtle, + borderRadius: radii.card, + paddingHorizontal: spacing.md, + paddingVertical: spacing.sm + 2, + gap: spacing.sm, + marginBottom: spacing.sm }, - skeletonLine: { - height: 12, - borderRadius: 4, + accountsHostLabel: { + fontSize: 11, + color: colors.textMuted, + fontWeight: '500', + textTransform: 'uppercase', + letterSpacing: 0.4 + }, + accountsRow: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.sm + 2 + }, + accountsIcon: { + width: 32, + height: 32, + borderRadius: 9, backgroundColor: colors.bgRaised, - opacity: 0.5 + alignItems: 'center', + justifyContent: 'center' + }, + accountsInfo: { + flex: 1, + minWidth: 0, + gap: 2 + }, + accountsEmail: { + fontSize: 13, + fontWeight: '600', + color: colors.textPrimary + }, + accountsBars: { + flexDirection: 'row', + gap: spacing.md, + marginTop: 4 }, /* ─── Quick actions ─── */ @@ -836,18 +1192,20 @@ const styles = StyleSheet.create({ }, quickAction: { flex: 1, + flexDirection: 'row', backgroundColor: colors.bgPanel, borderWidth: 1, borderColor: colors.borderSubtle, borderRadius: radii.card, - padding: spacing.lg, + paddingVertical: 10, + paddingHorizontal: 12, alignItems: 'center', gap: 10 }, quickActionIcon: { - width: 40, - height: 40, - borderRadius: 12, + width: 28, + height: 28, + borderRadius: 9, backgroundColor: 'rgba(255,255,255,0.04)', alignItems: 'center', justifyContent: 'center' @@ -855,8 +1213,7 @@ const styles = StyleSheet.create({ quickActionLabel: { fontSize: 12, fontWeight: '600', - color: colors.textSecondary, - textAlign: 'center' + color: colors.textSecondary }, /* ─── Empty state ─── */ diff --git a/mobile/app/notifications.tsx b/mobile/app/notifications.tsx index 7b0fdf9cd43..f7922074d6e 100644 --- a/mobile/app/notifications.tsx +++ b/mobile/app/notifications.tsx @@ -1,5 +1,5 @@ -import { useState, useCallback } from 'react' -import { View, Text, StyleSheet, Pressable, Switch } from 'react-native' +import { useState, useCallback, useEffect } from 'react' +import { AppState, Linking, View, Text, StyleSheet, Pressable, Switch } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useRouter, useFocusEffect } from 'expo-router' import { ChevronLeft } from 'lucide-react-native' @@ -8,28 +8,69 @@ import { loadPushNotificationsEnabled, savePushNotificationsEnabled } from '../src/storage/preferences' -import { ensureNotificationPermissions } from '../src/notifications/mobile-notifications' +import { + ensureNotificationPermissions, + getNotificationPermissionState, + type NotificationPermissionState +} from '../src/notifications/mobile-notifications' + +const DEFAULT_PERMISSION_STATE: NotificationPermissionState = { + granted: false, + status: 'undetermined', + canAskAgain: true +} export default function NotificationsScreen() { const router = useRouter() const insets = useSafeAreaInsets() - const [pushEnabled, setPushEnabled] = useState(true) + const [pushEnabled, setPushEnabled] = useState(false) + const [permissionState, setPermissionState] = useState(DEFAULT_PERMISSION_STATE) + + const refreshSettings = useCallback(async () => { + const [enabled, permission] = await Promise.all([ + loadPushNotificationsEnabled(), + getNotificationPermissionState() + ]) + setPushEnabled(enabled) + setPermissionState(permission) + }, []) useFocusEffect( useCallback(() => { - void loadPushNotificationsEnabled().then(setPushEnabled) - }, []) + void refreshSettings() + }, [refreshSettings]) ) + useEffect(() => { + const subscription = AppState.addEventListener('change', (state) => { + if (state === 'active') { + void refreshSettings() + } + }) + return () => subscription.remove() + }, [refreshSettings]) + const togglePush = async (value: boolean) => { if (value) { const granted = await ensureNotificationPermissions() - if (!granted) return + const permission = await getNotificationPermissionState() + setPermissionState(permission) + if (!granted) { + setPushEnabled(false) + await savePushNotificationsEnabled(false) + return + } } setPushEnabled(value) await savePushNotificationsEnabled(value) } + const switchEnabled = pushEnabled && permissionState.granted + const notificationsBlocked = permissionState.status === 'denied' + const hint = notificationsBlocked + ? 'Notifications are disabled in system settings.' + : 'Receive notifications when an agent task completes on your desktop.' + return ( @@ -43,15 +84,25 @@ export default function NotificationsScreen() { Push Notifications void togglePush(v)} trackColor={{ false: colors.bgRaised, true: colors.textSecondary }} thumbColor={colors.textPrimary} /> - - Receive notifications when an agent task completes on your desktop. - + {hint} + {notificationsBlocked && ( + [ + styles.settingsButton, + pressed && styles.settingsButtonPressed + ]} + onPress={() => void Linking.openSettings()} + > + Open Settings + + )} ) @@ -105,5 +156,22 @@ const styles = StyleSheet.create({ lineHeight: 18, paddingHorizontal: spacing.md + 2, paddingBottom: spacing.md + }, + settingsButton: { + alignSelf: 'flex-start', + marginHorizontal: spacing.md + 2, + marginBottom: spacing.md, + paddingVertical: spacing.xs, + paddingHorizontal: spacing.sm, + borderRadius: 8, + backgroundColor: colors.bgRaised + }, + settingsButtonPressed: { + opacity: 0.6 + }, + settingsButtonText: { + color: colors.textPrimary, + fontSize: typography.metaSize, + fontWeight: '600' } }) diff --git a/mobile/app/pair-confirm.tsx b/mobile/app/pair-confirm.tsx new file mode 100644 index 00000000000..2a63018aafe --- /dev/null +++ b/mobile/app/pair-confirm.tsx @@ -0,0 +1,256 @@ +import { useEffect, useRef, useState } from 'react' +import { View, Text, StyleSheet, Pressable, ActivityIndicator } from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { useLocalSearchParams, useRouter } from 'expo-router' +import { ChevronLeft } from 'lucide-react-native' +import { parsePairingCode } from '../src/transport/pairing' +import { connect } from '../src/transport/rpc-client' +import { saveHost, getNextHostName } from '../src/transport/host-store' +import type { ConnectionLogEntry, PairingOffer, RpcResponse } from '../src/transport/types' +import { colors, spacing, radii, typography } from '../src/theme/mobile-theme' +import { ConnectionLog } from '../src/components/ConnectionLog' + +type Status = 'awaiting-confirm' | 'connecting' | 'error' + +// Why: cap how long the user stares at "Connecting…" during pairing. +// rpc-client retries forever by design (good for live sessions), but for +// the *initial* pair we want a hard ceiling so a half-broken Tailscale +// route surfaces an actionable error with the log visible, instead of +// spinning silently. ~25s allows for one full connect-timeout + a retry. +const PAIRING_OVERALL_TIMEOUT_MS = 25_000 + +export default function PairConfirmScreen() { + const router = useRouter() + const insets = useSafeAreaInsets() + const params = useLocalSearchParams<{ code?: string }>() + const [offer, setOffer] = useState(null) + const [status, setStatus] = useState('awaiting-confirm') + const [errorMessage, setErrorMessage] = useState('') + const [logs, setLogs] = useState([]) + // Why: collect logs in a ref so the rpc-client callback (which closures + // over the initial state setter) always sees the freshest list and we + // batch fewer setState calls when entries arrive in bursts. + const logsRef = useRef([]) + + useEffect(() => { + if (!params.code) { + setStatus('error') + setErrorMessage('Missing pairing code') + return + } + const parsed = parsePairingCode(params.code) + if (!parsed) { + setStatus('error') + setErrorMessage('Not a valid pairing code') + return + } + setOffer(parsed) + }, [params.code]) + + async function confirm() { + if (!offer) return + setStatus('connecting') + logsRef.current = [] + setLogs([]) + let client: ReturnType | null = null + + // Why: split the try/catch around the network call vs the local save + // so a Keychain or AsyncStorage failure doesn't masquerade as a + // "Cannot connect" error. + let response: RpcResponse + let timedOut = false + const overallTimer = setTimeout(() => { + timedOut = true + client?.close() + }, PAIRING_OVERALL_TIMEOUT_MS) + try { + client = connect(offer.endpoint, offer.deviceToken, offer.publicKeyB64, { + onLog: (entry) => { + logsRef.current = [...logsRef.current, entry] + setLogs(logsRef.current) + } + }) + response = await client.sendRequest('status.get') + clearTimeout(overallTimer) + client.close() + client = null + } catch (err) { + clearTimeout(overallTimer) + console.warn('[pair-confirm] connect failed', err) + setStatus('error') + setErrorMessage( + timedOut + ? `Couldn't connect within ${PAIRING_OVERALL_TIMEOUT_MS / 1000}s — see log below for where it stalled` + : 'Cannot connect — check that your computer is on the same network' + ) + client?.close() + return + } + + if (!response.ok) { + setStatus('error') + setErrorMessage( + response.error.code === 'unauthorized' + ? 'Authentication failed — token may be expired' + : `Server error: ${response.error.message}` + ) + return + } + + try { + const hostId = `host-${Date.now()}` + const hostName = await getNextHostName() + await saveHost({ + id: hostId, + name: hostName, + endpoint: offer.endpoint, + deviceToken: offer.deviceToken, + publicKeyB64: offer.publicKeyB64, + lastConnected: Date.now() + }) + router.replace(`/h/${hostId}`) + } catch (err) { + console.warn('[pair-confirm] save failed', err) + setStatus('error') + setErrorMessage( + `Pairing succeeded but couldn't save the host: ${err instanceof Error ? err.message : String(err)}` + ) + } + } + + function cancel() { + router.replace('/') + } + + const containerPadding = { paddingTop: insets.top + spacing.sm } + + return ( + + + + + + + {offer && status === 'awaiting-confirm' && ( + <> + Pair with this desktop? + + You opened a pairing link from your desktop. Confirm to add it to your hosts. + + void confirm()}> + Pair + + + Cancel + + + )} + + {status === 'connecting' && ( + <> + + Connecting… + + + + + )} + + {status === 'error' && ( + <> + {errorMessage} + {logs.length > 0 && ( + + + + )} + + Back to home + + + )} + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.bgBase, + padding: spacing.lg + }, + backButton: { + width: 36, + height: 36, + borderRadius: 18, + alignItems: 'center', + justifyContent: 'center', + marginBottom: spacing.sm + }, + content: { + flex: 1, + justifyContent: 'center', + paddingHorizontal: spacing.sm, + // Why: nudges the centered group slightly above the geometric + // middle so the eye reads it as visually centered above the home + // indicator / nav bar. + paddingBottom: spacing.xl * 2 + }, + title: { + fontSize: typography.titleSize, + fontWeight: '600', + color: colors.textPrimary, + marginBottom: spacing.sm, + textAlign: 'center' + }, + subtitle: { + fontSize: typography.bodySize, + color: colors.textSecondary, + lineHeight: 20, + marginBottom: spacing.xl, + textAlign: 'center' + }, + primaryButton: { + backgroundColor: colors.textPrimary, + paddingHorizontal: spacing.xl, + paddingVertical: spacing.sm + 2, + borderRadius: radii.button, + alignItems: 'center', + marginBottom: spacing.sm + }, + primaryButtonText: { + color: colors.bgBase, + fontSize: typography.bodySize, + fontWeight: '600' + }, + secondaryButton: { + paddingHorizontal: spacing.xl, + paddingVertical: spacing.sm + 2, + borderRadius: radii.button, + alignItems: 'center' + }, + secondaryButtonText: { + color: colors.textSecondary, + fontSize: typography.bodySize, + fontWeight: '500' + }, + connectingText: { + color: colors.textSecondary, + fontSize: typography.bodySize, + marginTop: spacing.lg, + textAlign: 'center' + }, + logSlot: { + width: '100%', + marginTop: spacing.lg, + marginBottom: spacing.md + }, + errorText: { + color: colors.statusRed, + fontSize: typography.bodySize, + textAlign: 'center', + marginBottom: spacing.xl, + lineHeight: 20 + } +}) diff --git a/mobile/app/pair-scan.tsx b/mobile/app/pair-scan.tsx index ca522e7ddae..ca8a8673e7a 100644 --- a/mobile/app/pair-scan.tsx +++ b/mobile/app/pair-scan.tsx @@ -1,14 +1,21 @@ import { useState, useRef, useCallback } from 'react' -import { View, Text, StyleSheet, Pressable, ActivityIndicator } from 'react-native' +import { View, Text, StyleSheet, Pressable, ActivityIndicator, Linking } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { CameraView, useCameraPermissions } from 'expo-camera' import { useRouter } from 'expo-router' -import { ChevronLeft } from 'lucide-react-native' -import { decodePairingUrl } from '../src/transport/pairing' +import { ChevronLeft, Clipboard as ClipboardIcon, QrCode } from 'lucide-react-native' +import { decodePairingUrl, parsePairingCode } from '../src/transport/pairing' import { connect } from '../src/transport/rpc-client' import { saveHost, getNextHostName } from '../src/transport/host-store' -import type { PairingOffer } from '../src/transport/types' +import type { ConnectionLogEntry, PairingOffer, RpcResponse } from '../src/transport/types' import { colors, spacing, radii, typography } from '../src/theme/mobile-theme' +import { TextInputModal } from '../src/components/TextInputModal' +import { ConnectionLog } from '../src/components/ConnectionLog' + +// Why: see pair-confirm.tsx — cap initial-pair "Connecting…" so a broken +// route surfaces as a real error with the log visible instead of a +// silent infinite spinner. +const PAIRING_OVERALL_TIMEOUT_MS = 25_000 function Step({ number, text }: { number: number; text: string }) { return ( @@ -27,6 +34,9 @@ export default function PairScanScreen() { const [permission, requestPermission] = useCameraPermissions() const [status, setStatus] = useState<'scanning' | 'connecting' | 'error'>('scanning') const [errorMessage, setErrorMessage] = useState('') + const [pasteVisible, setPasteVisible] = useState(false) + const [logs, setLogs] = useState([]) + const logsRef = useRef([]) const processingRef = useRef(false) const handleBarCodeScanned = useCallback( @@ -47,32 +57,79 @@ export default function PairScanScreen() { [router] ) + const handlePasteSubmit = useCallback((input: string) => { + setPasteVisible(false) + if (processingRef.current) return + processingRef.current = true + + const offer = parsePairingCode(input) + if (!offer) { + setStatus('error') + setErrorMessage('Not a valid pairing code — copy it from your computer and paste again') + processingRef.current = false + return + } + + void testAndSave(offer) + }, []) + async function testAndSave(offer: PairingOffer) { setStatus('connecting') + logsRef.current = [] + setLogs([]) let client: ReturnType | null = null + // Why: split the try/catch around the network call vs the local save + // so a Keychain or AsyncStorage failure doesn't masquerade as a + // "Cannot connect — same network?" error. Pairing reached the + // desktop fine; the failure is local persistence. + let response: RpcResponse + let timedOut = false + const overallTimer = setTimeout(() => { + timedOut = true + client?.close() + }, PAIRING_OVERALL_TIMEOUT_MS) try { - client = connect(offer.endpoint, offer.deviceToken, offer.publicKeyB64) - const response = await client.sendRequest('status.get') + client = connect(offer.endpoint, offer.deviceToken, offer.publicKeyB64, { + onLog: (entry) => { + logsRef.current = [...logsRef.current, entry] + setLogs(logsRef.current) + } + }) + response = await client.sendRequest('status.get') + clearTimeout(overallTimer) client.close() client = null + } catch (err) { + clearTimeout(overallTimer) + console.warn('[pair] connect failed', err) + setStatus('error') + setErrorMessage( + timedOut + ? `Couldn't connect within ${PAIRING_OVERALL_TIMEOUT_MS / 1000}s — see log below for where it stalled` + : 'Cannot connect — check that your computer is on the same network' + ) + processingRef.current = false + client?.close() + return + } - if (!response.ok) { - if (response.error.code === 'unauthorized') { - setStatus('error') - setErrorMessage('Authentication failed — token may be expired') - processingRef.current = false - return - } + if (!response.ok) { + if (response.error.code === 'unauthorized') { setStatus('error') - setErrorMessage(`Server error: ${response.error.message}`) + setErrorMessage('Authentication failed — token may be expired') processingRef.current = false return } + setStatus('error') + setErrorMessage(`Server error: ${response.error.message}`) + processingRef.current = false + return + } + try { const hostId = `host-${Date.now()}` const hostName = await getNextHostName() - await saveHost({ id: hostId, name: hostName, @@ -81,24 +138,32 @@ export default function PairScanScreen() { publicKeyB64: offer.publicKeyB64, lastConnected: Date.now() }) - router.replace(`/h/${hostId}`) - } catch { + } catch (err) { + console.warn('[pair] save failed', err) setStatus('error') - setErrorMessage('Cannot connect — check that your computer is on the same network') + setErrorMessage( + `Pairing succeeded but couldn't save the host: ${err instanceof Error ? err.message : String(err)}` + ) processingRef.current = false - } finally { - client?.close() } } function retry() { setStatus('scanning') setErrorMessage('') + logsRef.current = [] + setLogs([]) processingRef.current = false } - const containerPadding = { paddingTop: insets.top + spacing.sm } + // Why: bottom inset accounts for Android 3-button nav bars and iOS + // home-indicator areas that would otherwise overlap the 'Or paste + // pairing code' button at the bottom of the scan screen. + const containerPadding = { + paddingTop: insets.top + spacing.sm, + paddingBottom: insets.bottom + spacing.sm + } if (!permission) { return ( @@ -109,20 +174,46 @@ export default function PairScanScreen() { } if (!permission.granted) { + const canAskAgain = permission.canAskAgain !== false return ( router.back()}> - Camera Permission - - Orca needs camera access to scan the pairing QR code from your desktop. + + {canAskAgain ? 'Pair with desktop' : 'Camera Access Disabled'} - - Grant Camera Access + + {canAskAgain + ? 'Scan the QR code from Orca on your desktop, or paste the pairing code instead.' + : 'Enable camera access in Settings, or paste the pairing code instead.'} + + void Linking.openSettings()} + > + {canAskAgain && } + + {canAskAgain ? 'Continue' : 'Open Settings'} + + + [styles.pasteButton, pressed && styles.pasteButtonPressed]} + onPress={() => setPasteVisible(true)} + > + + Paste code instead + setPasteVisible(false)} + /> ) } @@ -140,37 +231,85 @@ export default function PairScanScreen() { {status === 'scanning' && ( - - - - - - - - - + <> + {/* Why: unmount the camera while the paste sheet is open. The + user has clearly chosen the paste path; keeping the camera + streaming behind a sheet wastes power and looks weird if + they cancel the sheet and the QR was scanned silently in + the meantime. */} + {!pasteVisible && ( + + + + + + + + + + )} + {pasteVisible && } + [styles.pasteButton, pressed && styles.pasteButtonPressed]} + onPress={() => setPasteVisible(true)} + > + + Or paste pairing code + + )} {status === 'connecting' && ( Connecting… + + + )} {status === 'error' && ( {errorMessage} - - Try Again - + {logs.length > 0 && ( + + + + )} + + + Try Again + + [ + styles.secondaryButton, + pressed && styles.pasteButtonPressed + ]} + onPress={() => { + retry() + setPasteVisible(true) + }} + > + Paste code instead + + )} + + setPasteVisible(false)} + /> ) } @@ -221,6 +360,14 @@ const styles = StyleSheet.create({ borderRadius: radii.camera, overflow: 'hidden' }, + // Why: holds the layout slot while the camera is unmounted during + // paste, so the bottom action button doesn't snap up to fill the + // empty space. + cameraPlaceholder: { + flex: 1, + backgroundColor: colors.bgPanel, + borderRadius: radii.camera + }, camera: { ...StyleSheet.absoluteFillObject }, @@ -275,6 +422,7 @@ const styles = StyleSheet.create({ marginBottom: spacing.sm }, subtitle: { + maxWidth: 310, fontSize: typography.bodySize, color: colors.textSecondary, textAlign: 'center', @@ -286,6 +434,11 @@ const styles = StyleSheet.create({ fontSize: typography.bodySize, marginTop: spacing.lg }, + logSlot: { + width: '100%', + marginTop: spacing.lg, + paddingHorizontal: spacing.sm + }, errorText: { color: colors.statusRed, fontSize: typography.bodySize, @@ -294,6 +447,10 @@ const styles = StyleSheet.create({ lineHeight: 20 }, primaryButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: spacing.xs, backgroundColor: colors.textPrimary, paddingHorizontal: spacing.xl, paddingVertical: spacing.sm + 2, @@ -303,5 +460,36 @@ const styles = StyleSheet.create({ color: colors.bgBase, fontSize: typography.bodySize, fontWeight: '600' + }, + pasteButton: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + gap: spacing.xs, + marginTop: spacing.md, + paddingVertical: spacing.sm, + borderRadius: radii.button + }, + pasteButtonPressed: { + opacity: 0.6 + }, + pasteButtonText: { + color: colors.textSecondary, + fontSize: typography.bodySize, + fontWeight: '500' + }, + errorActions: { + alignItems: 'center', + gap: spacing.sm + }, + secondaryButton: { + paddingHorizontal: spacing.xl, + paddingVertical: spacing.sm, + borderRadius: radii.button + }, + secondaryButtonText: { + color: colors.textSecondary, + fontSize: typography.bodySize, + fontWeight: '500' } }) diff --git a/mobile/app/settings.tsx b/mobile/app/settings.tsx index 171bd004853..a189a8cd07b 100644 --- a/mobile/app/settings.tsx +++ b/mobile/app/settings.tsx @@ -1,7 +1,16 @@ -import { View, Text, StyleSheet, Pressable } from 'react-native' +import { View, Text, StyleSheet, Pressable, Linking } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useRouter } from 'expo-router' -import { ChevronLeft, ChevronRight, Info, Bell, Wrench } from 'lucide-react-native' +import { + ChevronLeft, + ChevronRight, + Info, + Bell, + Wrench, + Shield, + LifeBuoy, + Terminal as TerminalIcon +} from 'lucide-react-native' import { colors, spacing, typography } from '../src/theme/mobile-theme' export default function SettingsScreen() { @@ -18,6 +27,15 @@ export default function SettingsScreen() { + [styles.row, pressed && styles.rowPressed]} + onPress={() => router.push('/terminal-settings')} + > + + Terminal + + + [styles.row, pressed && styles.rowPressed]} onPress={() => router.push('/notifications')} @@ -45,6 +63,24 @@ export default function SettingsScreen() { + + + [styles.row, pressed && styles.rowPressed]} + onPress={() => void Linking.openURL('https://www.onorca.dev/privacy')} + > + + Privacy Policy + + + [styles.row, pressed && styles.rowPressed]} + onPress={() => void Linking.openURL('https://github.com/stablyai/orca/issues')} + > + + Support + + ) } @@ -78,6 +114,9 @@ const styles = StyleSheet.create({ borderRadius: 12, overflow: 'hidden' }, + sectionSpacer: { + marginTop: spacing.md + }, row: { flexDirection: 'row', alignItems: 'center', diff --git a/mobile/app/terminal-settings.tsx b/mobile/app/terminal-settings.tsx new file mode 100644 index 00000000000..abcf324a855 --- /dev/null +++ b/mobile/app/terminal-settings.tsx @@ -0,0 +1,289 @@ +import { useEffect, useMemo, useState } from 'react' +import { View, Text, StyleSheet, Pressable, ScrollView } from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' +import { useRouter } from 'expo-router' +import { ChevronLeft, ChevronRight, Smartphone } from 'lucide-react-native' +import { colors, spacing, typography } from '../src/theme/mobile-theme' +import { loadHosts } from '../src/transport/host-store' +import type { HostProfile } from '../src/transport/types' +import { useAllHostClients } from '../src/transport/client-context' +import type { RpcClient } from '../src/transport/rpc-client' +import { PickerModal, type PickerOption } from '../src/components/PickerModal' + +type RestoreValue = 'indefinite' | '60s' | '5m' | '30m' + +const AUTO_RESTORE_FIT_OPTIONS: (PickerOption & { ms: number | null })[] = [ + { value: 'indefinite', label: 'Keep at phone size (default)', ms: null }, + { value: '60s', label: 'After 1 minute', ms: 60_000 }, + { value: '5m', label: 'After 5 minutes', ms: 5 * 60_000 }, + { value: '30m', label: 'After 30 minutes', ms: 30 * 60_000 } +] + +function valueFromMs(ms: number | null | undefined): RestoreValue { + if (ms == null) return 'indefinite' + const exact = AUTO_RESTORE_FIT_OPTIONS.find((o) => o.ms === ms) + if (exact) return exact.value + // Why: server may return a non-preset ms (custom value, future preset, + // or server-side clamp). Snap to the closest finite preset so the + // picker's selected radio agrees with the row sublabel rendered by + // autoRestoreSummary ("After Xs"). + let closest: (typeof AUTO_RESTORE_FIT_OPTIONS)[number] | null = null + let bestDelta = Infinity + for (const opt of AUTO_RESTORE_FIT_OPTIONS) { + if (opt.ms == null) continue + const delta = Math.abs(opt.ms - ms) + if (delta < bestDelta) { + bestDelta = delta + closest = opt + } + } + return closest ? closest.value : 'indefinite' +} + +function autoRestoreSummary(ms: number | null | undefined): string { + if (ms === undefined) return '…' + if (ms === null) return AUTO_RESTORE_FIT_OPTIONS[0]!.label + const exact = AUTO_RESTORE_FIT_OPTIONS.find((o) => o.ms === ms) + return exact ? exact.label : `After ${Math.round(ms / 1000)}s` +} + +function HostFitRow({ + client, + hostName, + ms, + onPress +}: { + client: RpcClient | null + hostName: string + ms: number | null | undefined + onPress: () => void +}): React.JSX.Element { + return ( + [styles.row, pressed && styles.rowPressed]} + onPress={onPress} + disabled={!client} + > + + + {hostName} + {autoRestoreSummary(ms)} + + + + ) +} + +export default function TerminalSettingsScreen() { + const router = useRouter() + const insets = useSafeAreaInsets() + const [hosts, setHosts] = useState([]) + useEffect(() => { + void loadHosts().then(setHosts) + }, []) + const hostIds = useMemo(() => hosts.map((h) => h.id), [hosts]) + const hostClients = useAllHostClients(hostIds) + + // Why: per-host current value, lazily fetched. We keep state at the + // screen level rather than per-row so the picker can render at root + // level — embedding PickerModal inside a row clipped its BottomDrawer + // absoluteFill backdrop to the ScrollView content frame and made the + // drawer appear cut-off. + const [hostMs, setHostMs] = useState>({}) + const [pickerHostId, setPickerHostId] = useState(null) + + useEffect(() => { + let cancelled = false + for (const host of hosts) { + const entry = hostClients.find((e) => e.hostId === host.id) + const client = entry?.client ?? null + if (!client) continue + void client + .sendRequest('terminal.getAutoRestoreFit') + .then((resp) => { + if (cancelled) return + const value = (resp as { ms?: number | null } | null)?.ms + setHostMs((prev) => ({ ...prev, [host.id]: value === undefined ? null : value })) + }) + .catch(() => { + if (!cancelled) setHostMs((prev) => ({ ...prev, [host.id]: null })) + }) + } + return () => { + cancelled = true + } + }, [hosts, hostClients]) + + async function selectValue(hostId: string, value: RestoreValue) { + const entry = hostClients.find((e) => e.hostId === hostId) + const client = entry?.client ?? null + if (!client) return + const opt = AUTO_RESTORE_FIT_OPTIONS.find((o) => o.value === value) + if (!opt) return + setHostMs((prev) => ({ ...prev, [hostId]: opt.ms })) + try { + const resp = (await client.sendRequest('terminal.setAutoRestoreFit', { + ms: opt.ms + })) as { ms?: number | null } | null + const finalMs = resp?.ms === undefined ? null : resp.ms + setHostMs((prev) => ({ ...prev, [hostId]: finalMs })) + } catch { + try { + const resp = (await client.sendRequest('terminal.getAutoRestoreFit')) as { + ms?: number | null + } | null + setHostMs((prev) => ({ + ...prev, + [hostId]: resp?.ms === undefined ? null : resp.ms + })) + } catch { + // give up silently — the next mount retries + } + } + } + + const pickerHost = pickerHostId ? hosts.find((h) => h.id === pickerHostId) : null + + return ( + + + router.back()}> + + + Terminal + + + + WHEN YOU LEAVE THE APP + + While you're using a terminal on your phone, Orca shrinks it to fit your screen. When + you close the app or switch away, this controls whether it stays at phone size (so + interactive CLI tools don't reflow) or resizes back to your desktop. You can always + tap Restore on the terminal banner to resize it manually. + + + {hosts.length === 0 ? ( + + + No paired desktops yet. Pair one to control terminal behavior. + + + ) : ( + + {hosts.map((host, idx) => { + const entry = hostClients.find((e) => e.hostId === host.id) + return ( + + {idx > 0 && } + setPickerHostId(host.id)} + /> + + ) + })} + + )} + + + + visible={pickerHost != null} + title={pickerHost ? `Restore ${pickerHost.name}` : ''} + options={AUTO_RESTORE_FIT_OPTIONS} + selected={valueFromMs(pickerHost ? hostMs[pickerHost.id] : null)} + onSelect={(v) => { + if (pickerHost) void selectValue(pickerHost.id, v) + }} + onClose={() => setPickerHostId(null)} + /> + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.bgBase, + paddingHorizontal: spacing.lg, + paddingTop: 0 + }, + topRow: { + flexDirection: 'row', + alignItems: 'center', + marginTop: spacing.sm, + marginBottom: spacing.lg + }, + backButton: { + width: 36, + height: 36, + borderRadius: 18, + alignItems: 'center', + justifyContent: 'center', + marginRight: spacing.sm + }, + heading: { + fontSize: 20, + fontWeight: '700', + color: colors.textPrimary + }, + scrollContent: { + paddingBottom: spacing.xl + }, + groupHeading: { + fontSize: 11, + fontWeight: '600', + color: colors.textMuted, + letterSpacing: 0.5, + marginBottom: spacing.xs, + paddingHorizontal: spacing.xs + }, + groupDescription: { + fontSize: typography.bodySize - 1, + color: colors.textSecondary, + lineHeight: 20, + paddingHorizontal: spacing.xs + }, + section: { + backgroundColor: colors.bgPanel, + borderRadius: 12, + overflow: 'hidden' + }, + sectionTopGap: { + marginTop: spacing.sm + }, + emptyText: { + fontSize: typography.bodySize, + color: colors.textSecondary, + padding: spacing.md + }, + row: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.sm + 2, + paddingVertical: spacing.md, + paddingHorizontal: spacing.md + 2 + }, + rowPressed: { + backgroundColor: colors.bgRaised + }, + rowContent: { + flex: 1 + }, + rowLabel: { + fontSize: typography.bodySize, + fontWeight: '500', + color: colors.textPrimary + }, + rowSublabel: { + fontSize: typography.bodySize - 2, + color: colors.textSecondary, + marginTop: 2 + }, + separator: { + height: StyleSheet.hairlineWidth, + backgroundColor: colors.borderSubtle, + marginHorizontal: spacing.md + } +}) diff --git a/mobile/assets/icon.png b/mobile/assets/icon.png index d056b1f01e9..96d88074a77 100644 Binary files a/mobile/assets/icon.png and b/mobile/assets/icon.png differ diff --git a/mobile/package.json b/mobile/package.json index 6006461c802..f058a6da01b 100644 --- a/mobile/package.json +++ b/mobile/package.json @@ -13,32 +13,34 @@ }, "dependencies": { "@react-native-async-storage/async-storage": "^2.2.0", - "expo": "^55.0.17", + "expo": "^55.0.23", "expo-build-properties": "^55.0.13", - "expo-camera": "^55.0.16", - "expo-constants": "^55.0.15", + "expo-camera": "^55.0.18", + "expo-clipboard": "^55.0.13", + "expo-constants": "^55.0.16", "expo-crypto": "^55.0.14", "expo-haptics": "^55.0.14", - "expo-linking": "^55.0.14", - "expo-notifications": "^55.0.21", - "expo-router": "^55.0.13", - "expo-splash-screen": "^55.0.19", - "expo-status-bar": "^55.0.5", - "lucide-react-native": "^1.11.0", - "react": "^19.2.0", - "react-native": "^0.83.6", - "react-native-gesture-handler": "^2.30.1", - "react-native-reanimated": "^4.2.1", - "react-native-safe-area-context": "^5.6.2", - "react-native-screens": "^4.23.0", + "expo-linking": "^55.0.15", + "expo-notifications": "^55.0.22", + "expo-router": "^55.0.14", + "expo-secure-store": "^55.0.13", + "expo-splash-screen": "^55.0.20", + "expo-status-bar": "^55.0.6", + "lucide-react-native": "^1.14.0", + "react": "^19.2.6", + "react-native": "^0.83.9", + "react-native-gesture-handler": "^2.31.2", + "react-native-reanimated": "^4.3.0", + "react-native-safe-area-context": "^5.7.0", + "react-native-screens": "^4.24.0", "react-native-svg": "^15.15.4", "react-native-web": "^0.21.2", "react-native-webview": "^13.16.1", - "react-native-worklets": "^0.7.4", + "react-native-worklets": "^0.8.3", "tweetnacl": "^1.0.3", - "ws": "^8.18.0", - "zod": "^4.3.6", - "zustand": "^5.0.12" + "ws": "^8.20.0", + "zod": "~4.4.3", + "zustand": "^5.0.13" }, "devDependencies": { "@types/react": "^19.2.14", @@ -46,8 +48,8 @@ "@types/ws": "^8.18.1", "expo-module-scripts": "^55.0.2", "oxfmt": "^0.47.0", - "oxlint": "^1.62.0", - "tsx": "^4.19.4", + "oxlint": "^1.63.0", + "tsx": "^4.21.0", "typescript": "^5.9.3" } } diff --git a/mobile/pnpm-lock.yaml b/mobile/pnpm-lock.yaml index 11f0e80157f..15ccfae4061 100644 --- a/mobile/pnpm-lock.yaml +++ b/mobile/pnpm-lock.yaml @@ -10,106 +10,112 @@ importers: dependencies: '@react-native-async-storage/async-storage': specifier: ^2.2.0 - version: 2.2.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) + version: 2.2.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) expo: - specifier: ^55.0.17 - version: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + specifier: ^55.0.23 + version: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) expo-build-properties: specifier: ^55.0.13 - version: 55.0.13(expo@55.0.17) + version: 55.0.13(expo@55.0.23) expo-camera: - specifier: ^55.0.16 - version: 55.0.16(@types/emscripten@1.41.5)(expo@55.0.17)(react-native-web@0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^55.0.18 + version: 55.0.18(@types/emscripten@1.41.5)(expo@55.0.23)(react-native-web@0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + expo-clipboard: + specifier: ^55.0.13 + version: 55.0.13(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) expo-constants: - specifier: ^55.0.15 - version: 55.0.15(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) + specifier: ^55.0.16 + version: 55.0.16(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) expo-crypto: specifier: ^55.0.14 - version: 55.0.14(expo@55.0.17) + version: 55.0.14(expo@55.0.23) expo-haptics: specifier: ^55.0.14 - version: 55.0.14(expo@55.0.17) + version: 55.0.14(expo@55.0.23) expo-linking: - specifier: ^55.0.14 - version: 55.0.14(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^55.0.15 + version: 55.0.15(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) expo-notifications: - specifier: ^55.0.21 - version: 55.0.21(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + specifier: ^55.0.22 + version: 55.0.22(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) expo-router: + specifier: ^55.0.14 + version: 55.0.14(021822249f1df9b3c5f483d3fca100f4) + expo-secure-store: specifier: ^55.0.13 - version: 55.0.13(121105c3b042d5e83ab3c1d3b84ed55f) + version: 55.0.13(expo@55.0.23) expo-splash-screen: - specifier: ^55.0.19 - version: 55.0.19(expo@55.0.17)(typescript@5.9.3) + specifier: ^55.0.20 + version: 55.0.20(expo@55.0.23)(typescript@5.9.3) expo-status-bar: - specifier: ^55.0.5 - version: 55.0.5(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^55.0.6 + version: 55.0.6(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) lucide-react-native: - specifier: ^1.11.0 - version: 1.11.0(react-native-svg@15.15.4(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^1.14.0 + version: 1.14.0(react-native-svg@15.15.4(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) react: - specifier: ^19.2.0 - version: 19.2.0 + specifier: ^19.2.6 + version: 19.2.6 react-native: - specifier: ^0.83.6 - version: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + specifier: ^0.83.9 + version: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) react-native-gesture-handler: - specifier: ^2.30.1 - version: 2.30.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^2.31.2 + version: 2.31.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) react-native-reanimated: - specifier: ^4.2.1 - version: 4.2.1(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^4.3.0 + version: 4.3.0(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) react-native-safe-area-context: - specifier: ^5.6.2 - version: 5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^5.7.0 + version: 5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) react-native-screens: - specifier: ^4.23.0 - version: 4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^4.24.0 + version: 4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) react-native-svg: specifier: ^15.15.4 - version: 15.15.4(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + version: 15.15.4(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) react-native-web: specifier: ^0.21.2 - version: 0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0) + version: 0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6) react-native-webview: specifier: ^13.16.1 - version: 13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + version: 13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) react-native-worklets: - specifier: ^0.7.4 - version: 0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + specifier: ^0.8.3 + version: 0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) tweetnacl: specifier: ^1.0.3 version: 1.0.3 ws: - specifier: ^8.18.0 + specifier: ^8.20.0 version: 8.20.0 zod: - specifier: ^4.3.6 - version: 4.3.6 + specifier: ~4.4.3 + version: 4.4.3 zustand: - specifier: ^5.0.12 - version: 5.0.12(@types/react@19.2.14)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)) + specifier: ^5.0.13 + version: 5.0.13(@types/react@19.2.14)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6)) devDependencies: '@types/react': specifier: ^19.2.14 version: 19.2.14 '@types/react-native': specifier: ^0.73.0 - version: 0.73.0(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + version: 0.73.0(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) '@types/ws': specifier: ^8.18.1 version: 8.18.1 expo-module-scripts: specifier: ^55.0.2 - version: 55.0.2(@babel/core@7.29.0)(@babel/runtime@7.29.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(eslint@9.39.4)(expo@55.0.17)(jest@29.7.0(@types/node@25.6.0))(prettier@2.8.8)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react-refresh@0.14.2)(react-test-renderer@19.2.0(react@19.2.0))(react@19.2.0) + version: 55.0.2(@babel/core@7.29.0)(@babel/runtime@7.29.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(eslint@9.39.4)(expo@55.0.23)(jest@29.7.0(@types/node@25.6.0))(prettier@2.8.8)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react-refresh@0.14.2)(react-test-renderer@19.2.0(react@19.2.6))(react@19.2.6) oxfmt: specifier: ^0.47.0 version: 0.47.0 oxlint: - specifier: ^1.62.0 - version: 1.62.0 + specifier: ^1.63.0 + version: 1.63.0 tsx: - specifier: ^4.19.4 + specifier: ^4.21.0 version: 4.21.0 typescript: specifier: ^5.9.3 @@ -128,8 +134,8 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} '@babel/core@7.29.0': @@ -148,8 +154,8 @@ packages: resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.6': - resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + '@babel/helper-create-class-features-plugin@7.29.3': + resolution: {integrity: sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -227,8 +233,8 @@ packages: resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.29.2': - resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true @@ -250,6 +256,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3': + resolution: {integrity: sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} engines: {node: '>=6.9.0'} @@ -436,12 +448,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.27.1': - resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.28.6': resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} engines: {node: '>=6.9.0'} @@ -454,12 +460,6 @@ packages: peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.28.4': - resolution: {integrity: sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-classes@7.28.6': resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} engines: {node: '>=6.9.0'} @@ -574,8 +574,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-modules-systemjs@7.29.0': - resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} + '@babel/plugin-transform-modules-systemjs@7.29.4': + resolution: {integrity: sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -598,12 +598,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1': - resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} engines: {node: '>=6.9.0'} @@ -634,12 +628,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.27.1': - resolution: {integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.28.6': resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} engines: {node: '>=6.9.0'} @@ -790,8 +778,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.29.2': - resolution: {integrity: sha512-DYD23veRYGvBFhcTY1iUvJnDNpuqNd/BzBwCvzOTKUnJjKg5kpUBh3/u9585Agdkgj+QuygG7jLfOPWMa2KVNw==} + '@babel/preset-env@7.29.5': + resolution: {integrity: sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -807,12 +795,6 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/preset-typescript@7.27.1': - resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/preset-typescript@7.28.5': resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} engines: {node: '>=6.9.0'} @@ -1039,8 +1021,8 @@ packages: '@expo-google-fonts/material-symbols@0.4.34': resolution: {integrity: sha512-PdwETUhvu1gHF1e8eIyEHnBJLq/dRNoTrT5yhsGUfGyRxH5pbm54dF3+QPknxwMKj0M1trN7PSelYz+yzlt3lA==} - '@expo/cli@55.0.26': - resolution: {integrity: sha512-Ud9gpeGMF5RIL42LXvCw3k3mWK8rf/P2wu+Yrzz9Do1kcFKZeT9Vy2D/xukjdr/Xw+ELba87ThOot17GsPiWjw==} + '@expo/cli@55.0.29': + resolution: {integrity: sha512-r2dXQ82e/3nwxS7faLRL6HBD8UWDo/IyptQ0Vg6Z5Bgyp2Kd24h8xPn3RHfY3LLJ3wfEXglf4E79/Dqkm1Z6WA==} hasBin: true peerDependencies: expo: '*' @@ -1061,14 +1043,14 @@ packages: '@expo/config-types@55.0.5': resolution: {integrity: sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg==} - '@expo/config@55.0.15': - resolution: {integrity: sha512-lHc0ELIQ8126jYOMZpLv3WIuvordW98jFg5aT/J1/12n2ycuXu01XLZkJsdw0avO34cusUYb1It+MvY8JiMduA==} + '@expo/config@55.0.16': + resolution: {integrity: sha512-H5dpQv5TfyZDNheZAWO3SmP10diGWZwN5QOUsArkDJih0QKNtahQBOmrV2xbhgln/nrUGoy41U/ZIY/MEx63Ug==} '@expo/devcert@1.2.1': resolution: {integrity: sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==} - '@expo/devtools@55.0.2': - resolution: {integrity: sha512-4VsFn9MUriocyuhyA+ycJP3TJhUsOFHDc270l9h3LhNpXMf6wvIdGcA0QzXkZtORXmlDybWXRP2KT1k36HcQkA==} + '@expo/devtools@55.0.3': + resolution: {integrity: sha512-KoIDgo0NoXeWLsIcOdZqtAG/1LlsM+JL0DA3bo0vCYaOYTBLXi/ZvRBqa20Ub8D2vKLNa+FgRQW0gRg04Ps1Pg==} peerDependencies: react: '*' react-native: '*' @@ -1085,22 +1067,22 @@ packages: react: '*' react-native: '*' - '@expo/env@2.1.1': - resolution: {integrity: sha512-rVvHC4I6xlPcg+mAO09ydUi2Wjv1ZytpLmHOSzvXzBAz9mMrJggqCe4s4dubjJvi/Ino/xQCLhbaLCnTtLpikg==} + '@expo/env@2.1.2': + resolution: {integrity: sha512-RJtGFfj/ygO/6zcVbV3cckHf4THcEkv5IZft1GjCB3dfT6axvzvIwXE9EiQqQYmGHcQ+ZrvC8xZcIhiHba0pYg==} engines: {node: '>=20.12.0'} - '@expo/fingerprint@0.16.6': - resolution: {integrity: sha512-nRITNbnu3RKSHPvKVehrSU4KG2VY9V8nvULOHBw98ukHCAU4bGrU5APvcblOkX3JAap+xEHsg/mZvqlvkLInmQ==} + '@expo/fingerprint@0.16.7': + resolution: {integrity: sha512-BH8sicYOqZ1iBMwCVEGIz6uTTfylosjc49FoMmCYIzKOiYdiVehsfoYBwyfxwWIiya1VMhm1gv0cgOP8fxHpDw==} hasBin: true - '@expo/image-utils@0.8.13': - resolution: {integrity: sha512-1I//yBQeTY6p0u1ihqGNDAr35EbSG8uFEupFrIF0jd++h9EWH33521yZJU1yE+mwGlzCb61g3ehu78siMhXBlA==} + '@expo/image-utils@0.8.14': + resolution: {integrity: sha512-5Sn+jG4Cw+shC2wDMXoqSAJnvERbiwzHn05FpWtD5IBflfTIs5gUmjzwiGVyjOdlMSQhgRrw/AymPbmO9h9mpQ==} - '@expo/json-file@10.0.13': - resolution: {integrity: sha512-pX/XjQn7tgNw6zuuV2ikmegmwe/S7uiwhrs2wXrANMkq7ozrA+JcZwgW9Q/8WZgciBzfAhNp5hnackHcrmapQA==} + '@expo/json-file@10.0.14': + resolution: {integrity: sha512-yWwBFywFv+SxkJp/pIzzA416JVYflNUh7pqQzgaA6nXDqRyK7KfrqVzk8PdUfDnqbBcaZZxpzNssfQZzp5KHrA==} - '@expo/local-build-cache-provider@55.0.11': - resolution: {integrity: sha512-rJ4RTCrkeKaXaido/bVyhl90ZRtVTOEbj59F1PWVjIEIVgjdlfc1J3VD9v7hEsbf/+8Tbr/PgvWhT6Visi5sLQ==} + '@expo/local-build-cache-provider@55.0.12': + resolution: {integrity: sha512-Wqhe7ajt6lyIEQvqDC1zm0MQ1RqQLlM9awCepY9pz+tm9rvhuxGPZTSddWeD8k4kolinBlDbLDFnNi06XgaDWQ==} '@expo/log-box@55.0.11': resolution: {integrity: sha512-JQHFLWkskIbJi6cxYMjErx8lQqfFJilDQLKmdTO3m3YkdmN9GE/CrzjOfVlCG0DGEGZJ90br0pGKvGPdXNsHKw==} @@ -1110,8 +1092,16 @@ packages: react: '*' react-native: '*' - '@expo/metro-config@55.0.17': - resolution: {integrity: sha512-o11VyNoRDXv0T5320D9cH+nSsrR/OMHTjtysKLIfDlidsBswDk1DMApPv9Kw0/gluArCSnbx8JC1G0Yh2Y4P3g==} + '@expo/log-box@55.0.12': + resolution: {integrity: sha512-f9ARS8J60cq3LLNdIqmUjYwyerBzVS5Ecp7KjIf3GOIPjW0571rkcwLz4/U18l/1DeSkSzIkYsNl2TC9oTdWaQ==} + peerDependencies: + '@expo/dom-webview': ^55.0.6 + expo: '*' + react: '*' + react-native: '*' + + '@expo/metro-config@55.0.20': + resolution: {integrity: sha512-dUv0simEyPbN2wbOjI+BdEZyXdghgCZD0+3rrA1WxXZN1lRofUx6g2+Nik2Qg61v/BXFrCTh8reYEzQPzHOhdQ==} peerDependencies: expo: '*' peerDependenciesMeta: @@ -1129,45 +1119,45 @@ packages: react-dom: optional: true - '@expo/metro@55.1.0': - resolution: {integrity: sha512-bb/LOncsz9KiP6cHmMy0MCDG1COZOn+k+pRpDrvJUmxLdOOuniJSYyCc/Dgv1bR9E/6YR+fh3EXGg9MUrVNy4Q==} + '@expo/metro@55.1.1': + resolution: {integrity: sha512-/wfXo5hTuAVpVLG/4hzlmD9NBGJkzkmBEMm/4VICajYRbj7y8OmqqPWbbymzHiBiHB6tI9BnsyXpQM6zVZEECg==} '@expo/npm-proofread@1.0.1': resolution: {integrity: sha512-yDyBlNIgg+rKoKCOVM9ZyAtgqRx6gHw+JtmZTrYYW4auAfwS6kE/iInmCpVXPqRy0OhJHgXj6q5PCgWELLVMeg==} hasBin: true - '@expo/osascript@2.4.2': - resolution: {integrity: sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw==} + '@expo/osascript@2.4.3': + resolution: {integrity: sha512-wbuj3EebM7W9hN/Wp4xTzKd6rQ2zKJzAxkFxkOOwyysLp0HOAgQ4/5RINyoS241pZUX2rUHq7mAJ7pcCQ8U0Ow==} engines: {node: '>=12'} - '@expo/package-manager@1.10.4': - resolution: {integrity: sha512-y9Mr4Kmpk4abAVZrNNPCdzOZr8nLLyi18p1SXr0RCVA8IfzqZX/eY4H+50a0HTmXqIsPZrQdcdb4I3ekMS9GvQ==} + '@expo/package-manager@1.10.5': + resolution: {integrity: sha512-nCP9Mebfl3jvOr0/P6VAuyah6PAtun+aihIL2zAtuE8uSe94JWkVZ7051i0MUVO+y3gFpBqnr8IIH5ch+VJjHA==} - '@expo/plist@0.5.2': - resolution: {integrity: sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==} + '@expo/plist@0.5.3': + resolution: {integrity: sha512-jz5oPcPDd3fygwVxwSwmO6wodTwm0Qa14NUyPy0ka7H8sFmCtNZUI2+DzVe/EXjOhq1FbEjrwl89gdlWYOnVjQ==} - '@expo/prebuild-config@55.0.16': - resolution: {integrity: sha512-o4EAVgDGk1lISirtMD8hciO2vyMp7cWlPdfTtjjd5AXSfODVYDIDhygXrfvVQHmJXAztVqPUTKJT+BYOsVkYGQ==} + '@expo/prebuild-config@55.0.17': + resolution: {integrity: sha512-Mcs+dg4Ripu0yCtzf66KZr18PehI1O8HxzJw+G5SUF8VWX+ic99aci1PltvmydWepLwTQL6ykmpXicAUA31IqA==} peerDependencies: expo: '*' - '@expo/require-utils@55.0.4': - resolution: {integrity: sha512-JAANvXqV7MOysWeVWgaiDzikoyDjJWOV/ulOW60Zb3kXJfrx2oZOtGtDXDFKD1mXuahQgoM5QOjuZhF7gFRNjA==} + '@expo/require-utils@55.0.5': + resolution: {integrity: sha512-U4K/CQ2VpXuwfNGsN+daKmYOt15hCP8v/pXaYH6eut7kdYZo6SfJ1yr67BIcJ+1Gzzs+QzTxswAZChKpXmceyw==} peerDependencies: typescript: ^5.0.0 || ^5.0.0-0 peerDependenciesMeta: typescript: optional: true - '@expo/router-server@55.0.15': - resolution: {integrity: sha512-6LksYO4Pg13qroL138KfUebt/x/EO07zVhdyT/nTgcxnpn6CS4ecTl3DciSKhxbaH+0BVLdANkxYeGdp43TMwQ==} + '@expo/router-server@55.0.16': + resolution: {integrity: sha512-LvAdrm039nQBG+95+ff5Rc4CsBuoc/giDhjQrgxB9lKJqC/ZTq1xbwfEZFNq6yokX6fOCs/vlxdhmSkOjMIrvg==} peerDependencies: - '@expo/metro-runtime': ^55.0.10 + '@expo/metro-runtime': ^55.0.11 expo: '*' - expo-constants: ^55.0.15 - expo-font: ^55.0.6 + expo-constants: ^55.0.16 + expo-font: ^55.0.7 expo-router: '*' - expo-server: ^55.0.8 + expo-server: ^55.0.9 react: '*' react-dom: '*' react-server-dom-webpack: ~19.0.1 || ~19.1.2 || ~19.2.1 @@ -1181,8 +1171,8 @@ packages: react-server-dom-webpack: optional: true - '@expo/schema-utils@55.0.3': - resolution: {integrity: sha512-l9KHVjTo6MvoeyvwNr6AjckGJm8NIcqZ3QSAh51cWozXW9v2AUjyCyqYtFtyntLWRZ0x/ByYJishpQo4ZQq45Q==} + '@expo/schema-utils@55.0.4': + resolution: {integrity: sha512-65IdeeE8dAZR3n3J5Eq7LYiQ8BFGeEYCWPBCzycvafL7PkskbCyIclTQarRwf/HXFoRvezKCjaLwy/8v9Prk6g==} '@expo/sdk-runtime-versions@1.0.0': resolution: {integrity: sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==} @@ -1204,8 +1194,8 @@ packages: '@expo/ws-tunnel@1.0.6': resolution: {integrity: sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==} - '@expo/xcpretty@4.4.3': - resolution: {integrity: sha512-wC562eD3gS6vO2tWHToFhlFnmHKfKHgF1oyvojeSkLK/ZYop1bMU+7cOMiF9Sq70CzcsLy/EMRy/uRc76QmNRw==} + '@expo/xcpretty@4.4.4': + resolution: {integrity: sha512-4aQzz9vgxcNXFfo/iyNgDDYfsU5XGKKxWxZopw0cVotHiW+U8IJbIxMaxsINs6bHhtkG3StKNPcOrn3eBuxKPw==} hasBin: true '@humanfs/core@0.19.2': @@ -1458,116 +1448,116 @@ packages: cpu: [x64] os: [win32] - '@oxlint/binding-android-arm-eabi@1.62.0': - resolution: {integrity: sha512-pKsthNECyvJh8lPTICz6VcwVy2jOqdhhsp1rlxCkhgZR47aKvXPmaRWQDv+zlXpRae4qm1MaaTnutkaOk5aofg==} + '@oxlint/binding-android-arm-eabi@1.63.0': + resolution: {integrity: sha512-A9xLtQt7i0OA1PoB/meog6kikXI9CdwEp7ZwQqmgnpKn3G3b1orvTDy8CQ6T7w1HvDrgWGB78PkFKcWgibcTCg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxlint/binding-android-arm64@1.62.0': - resolution: {integrity: sha512-b1AUNViByvgmR2xJDubvLIr+dSuu3uraG7bsAoKo+xrpspPvu6RIn6Fhr2JUhobfep3jwUTy18Huco6GkwdvGQ==} + '@oxlint/binding-android-arm64@1.63.0': + resolution: {integrity: sha512-SQo+ZMvdR9l3CxZp5W5gFNxSiDxclY6lOzzNpKYLF8asESpm3Pwumx0gER5T7aHLF1/2BAAtLD3DiDkdgy4V1A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxlint/binding-darwin-arm64@1.62.0': - resolution: {integrity: sha512-iG+Tvf70UJ6otfwFYIHk36Sjq9cpPP5YLxkoggANNRtzgi3Tj3g8q6Ybqi6AtkU3+yg9QwF7bDCkCS6bbL4PCg==} + '@oxlint/binding-darwin-arm64@1.63.0': + resolution: {integrity: sha512-6W82XjJDTmMnjg30427l0dufpnyLoq7wEukKdM6/g2VIybRVuQiBVh43EA4b+UxZ3+tLcKm+Or/pXGNgLCEU8g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxlint/binding-darwin-x64@1.62.0': - resolution: {integrity: sha512-oOWI6YPPr5AJUx+yIDlxmuUbQjS5gZX3OH3QisawYvsZgLiQVvZtR0rPBcJTxLWqt2ClrWg0DlSrlUiG5SQNHg==} + '@oxlint/binding-darwin-x64@1.63.0': + resolution: {integrity: sha512-CnWd/YCuVG5W1BYkjJEVbJG11o526O9qAwBEQM+nh8K19CRFUkFdROXCyYkGmroHEYQe4vgQ6+lh3550Lp35Xw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxlint/binding-freebsd-x64@1.62.0': - resolution: {integrity: sha512-dLP33T7VLCmLVv4cvjkVX+rmkcwNk2UfxmsZPNur/7BQHoQR60zJ7XLiRvNUawlzn0u8ngCa3itjEG73MAMa/w==} + '@oxlint/binding-freebsd-x64@1.63.0': + resolution: {integrity: sha512-a4eZAqrmtajqcxfdAzC+l7g3PaE3V8hpAYqqeD3fTxLXOMFdK3eNTZrU80n4dDEVm0JXy1aL5PqvqWldBl6zYA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxlint/binding-linux-arm-gnueabihf@1.62.0': - resolution: {integrity: sha512-fl//LWNks6qo9chNY60UDYyIwtp7a5cEx4Y/rHPjaarhuwqx6jtbzEpD5V5AqmdL4a6Y5D8zeXg5HF2Cr0QmSQ==} + '@oxlint/binding-linux-arm-gnueabihf@1.63.0': + resolution: {integrity: sha512-tYUtU9TdbU3uXF5D62g5zXJ13iniFGhXQx5vp9cyEjGdbSAY3VdFBSaldYvyoDmgMZ0ZYuwQP1Y4t2Fhejwa0w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm-musleabihf@1.62.0': - resolution: {integrity: sha512-i5vkAuxvueTODV3J2dL61/TXewDHhMFKvtD156cIsk7GsdfiAu7zW7kY0NJXhKeFHeiMZIh7eFNjkPYH6J47HQ==} + '@oxlint/binding-linux-arm-musleabihf@1.63.0': + resolution: {integrity: sha512-I5r3twFf776UZg9dmRo2xbrKt00tTkORXEVe0ctg4vdTkQvJAjiCHxnbAU2HL1AiJ9cqADA76MAliuilsAWnvg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxlint/binding-linux-arm64-gnu@1.62.0': - resolution: {integrity: sha512-QwN19LLuIGuOjEflSeJkZmOTfBdBMlTmW8xbMf8TZhjd//cxVNYQPq75q7oKZBJc6hRx3gY7sX0Egc8cEIFZYg==} + '@oxlint/binding-linux-arm64-gnu@1.63.0': + resolution: {integrity: sha512-t7ltUkg6FFh4b564QyGir8xIj/QZbXu8FlcRkcyW9+ztr/mfRHlvUOFd95pJCXi9s/L5DrUeWWgpXRS+V+6igQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxlint/binding-linux-arm64-musl@1.62.0': - resolution: {integrity: sha512-8eCy3FCDuWUM5hWujAv6heMvfZPbcCOU3SdQUAkixZLu5bSzOkNfirJiLGoQFO943xceOKkiQRMQNzH++jM3WA==} + '@oxlint/binding-linux-arm64-musl@1.63.0': + resolution: {integrity: sha512-Q5mmZy/XWjuYFUuQyYjOvZ5U/JkKEwnpir6hGxhh6HcdP0V/BKxLo8dqkfF/t7r7AguB17dfS/8+go5AQDRR6g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxlint/binding-linux-ppc64-gnu@1.62.0': - resolution: {integrity: sha512-NjQ7K7tpTPDe9J+yq8p/s/J0E7lRCkK2uDBDqvT4XIT6f4Z0tlnr59OBg/WcrmVHER1AbrcfyxhGTXgcG8ytWg==} + '@oxlint/binding-linux-ppc64-gnu@1.63.0': + resolution: {integrity: sha512-uBGtuZ0TzLB4x5wVa82HGNvYqY8buwDhyCnCP0R0gkk9szqVsP0MeTtD5HX7EsEuFIt+aYmYxuxeVxs3nTSwtQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - '@oxlint/binding-linux-riscv64-gnu@1.62.0': - resolution: {integrity: sha512-oKZed9gmSwze29dEt3/Wnsv6l/Ygw/FUst+8Kfpv2SGeS/glEoTGZAMQw37SVyzFV76UTHJN2snGgxK2t2+8ow==} + '@oxlint/binding-linux-riscv64-gnu@1.63.0': + resolution: {integrity: sha512-h4s6FwxE+9MeA181o0dnDwHP32Y/bG8EiB/vrD6Ib+AMt6haigDc/0bUtI/sLmQDBMJnUfaCmtSSrEAqjtEVrA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - '@oxlint/binding-linux-riscv64-musl@1.62.0': - resolution: {integrity: sha512-gBjBxQ+9lGpAYq+ELqw0w8QXsBnkZclFc7GRX2r0LnEVn3ZTEqeIKpKcGjucmp76Q53bvJD0i4qBWBhcfhSfGA==} + '@oxlint/binding-linux-riscv64-musl@1.63.0': + resolution: {integrity: sha512-2EaNcCBR8Mcjl5ARtuN3BdEpVkX7KpjSjMGZ/mJMIeaXgTtdz5ytg2VwygMSStA/k0ixfvZFoZOfjDEcouV5vQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - '@oxlint/binding-linux-s390x-gnu@1.62.0': - resolution: {integrity: sha512-Ew2Kxs9EQ9/mbAIJ2hvocMC0wsOu6YKzStI2eFBDt+Td5O8seVC/oxgRIHqCcl5sf5ratA1nozQBAuv7tphkHg==} + '@oxlint/binding-linux-s390x-gnu@1.63.0': + resolution: {integrity: sha512-p4hlf/fd7TrYYl3QrWWD0GocqJefwMu3cHQhmi2FvEB/YOvFb5DZN3SMBaPi7B1TM5DeypkEtrVib674q1KKPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@oxlint/binding-linux-x64-gnu@1.62.0': - resolution: {integrity: sha512-5z25jcAA0gfKyVwz71A0VXgaPlocPoTAxhlv/hgoK6tlCrfoNuw7haWbDHvGMfjXhdic4EqVXGRv5XsTqFnbRQ==} + '@oxlint/binding-linux-x64-gnu@1.63.0': + resolution: {integrity: sha512-Vgq9rkRVcPcjbcH+ihYTfpeR7vCXfqpd+z5ItTGc0yYUV59L5ceHYN1iV4H9bKGV7Rn5hkVc7x3mSvHegduENA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxlint/binding-linux-x64-musl@1.62.0': - resolution: {integrity: sha512-IWpHmMB6ZDllPvqWDkG6AmXrN7JF5e/c4g/0PuURsmlK+vHoYZPB70rr4u1bn3I4LsKCSpqqfveyx6UCOC8wdg==} + '@oxlint/binding-linux-x64-musl@1.63.0': + resolution: {integrity: sha512-3/Lkq/ncooA61rorrC+ZQed1Bc4VpGj+WnGsp58zmxKgvZ2vhreu+dcVyr3mX8NUpq7mfZ4gDDTou/yrF1Pd7A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxlint/binding-openharmony-arm64@1.62.0': - resolution: {integrity: sha512-fjlSxxrD5pA594vkyikCS9MnPRjQawW6/BLgyTYkO+73wwPlYjkcZ7LSd974l0Q2zkHQmu4DPvJFLYA7o8xrxQ==} + '@oxlint/binding-openharmony-arm64@1.63.0': + resolution: {integrity: sha512-0/EdD/6hDkx5Mfd769PTjvEM8mZ/6Dfukp1dBCL/2PjlIVGEtYdNZyok6ChqYPsT9JcFnlQnUeQzO0/1L/oC9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxlint/binding-win32-arm64-msvc@1.62.0': - resolution: {integrity: sha512-EiFXr8loNS0Ul3Gu80+9nr1T8jRmnKocqmHHg16tj5ZqTgUXyb97l2rrspVHdDluyFn9JfR4PoJFdNzw4paHww==} + '@oxlint/binding-win32-arm64-msvc@1.63.0': + resolution: {integrity: sha512-wb0CUkN8ngwPiRQBjD1Cj0LsHeNvm+Xt6YBHDMtj2DVQVD6Oj8Ri7g6BD+KICf6LaBqZlmzOvy6nF9E/8yyGOg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxlint/binding-win32-ia32-msvc@1.62.0': - resolution: {integrity: sha512-IgOFvL73li1bFgab+hThXYA0N2Xms2kV2MvZN95cebV+fmrZ9AVui1JSxfeeqRLo3CpPxKZlzhyq4G0cnaAvIw==} + '@oxlint/binding-win32-ia32-msvc@1.63.0': + resolution: {integrity: sha512-BX5iq+ovdNlVYhSn5qPMUIT0uwAwt2lmEnCnzK+Gkhw4DovIvhGb96OFhV8yzQNUnQxn/xGkOR+X+BLrLDNm8w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxlint/binding-win32-x64-msvc@1.62.0': - resolution: {integrity: sha512-6hMpyDWQ2zGA1OXFKBrdYMUveUCO8UJhkO6JdwZPd78xIdHZNhjx+pib+4fC2Cljuhjyl0QwA2F3df/bs4Bp6A==} + '@oxlint/binding-win32-x64-msvc@1.63.0': + resolution: {integrity: sha512-QeN/WELOfsXMeYwxvfgQrl6CbVftYUCZsGXHjXQd5Trccm8+i4gmtxaOui4xbJQaiDlviF8F3yLSBloQUeFsfA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -1809,14 +1799,10 @@ packages: peerDependencies: react-native: ^0.0.0-0 || >=0.65 <1.0 - '@react-native/assets-registry@0.83.6': - resolution: {integrity: sha512-iljb4ue1yWJ3EhySz7EjV6CzSVrI2uNtR8BI2jzP5+QS5E4Cl3fdIJRmVwDEx1pu8uE97PGEusGRHnoaZ9Q3jg==} + '@react-native/assets-registry@0.83.9': + resolution: {integrity: sha512-9MrShFZWvHybyjN8nj/rKW53hLpPOXMdEF8FOdDXRNu8oQ396BLknCMFeSCFhAHiCDVqQfah5iIV8S1wqXnxFA==} engines: {node: '>= 20.19.4'} - '@react-native/assets-registry@0.85.2': - resolution: {integrity: sha512-kauC/oPaxklU4Y+u9gBfCBJm51qX6WBZq4xx0USCdimtp+G8+554kpygfSWIjoqCJa2o06bWxBEjesiuCv+LzA==} - engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - '@react-native/babel-plugin-codegen@0.83.6': resolution: {integrity: sha512-qfRXsHGeucT5c6mK+8Q7v4Ly3zmygfVmFlEtkiq7q07W1OTreld6nib4rJ/DBEeNiKBoBTuHjWliYGNuDjLFQA==} engines: {node: '>= 20.19.4'} @@ -1843,14 +1829,20 @@ packages: peerDependencies: '@babel/core': '*' + '@react-native/codegen@0.83.9': + resolution: {integrity: sha512-fUliGYOD1D/hS4pZMK+YyGb01DxQQNUTjL+i2iL6elt2AncXoYMWNxLT/AMT38jImxI4FbKyH4JO+mL2+t5yuA==} + engines: {node: '>= 20.19.4'} + peerDependencies: + '@babel/core': '*' + '@react-native/codegen@0.85.2': resolution: {integrity: sha512-XCginmxh0//++EXVOEJHBVZxHla294FzLCFF6jXwAUjvXVhqyIKyxhABfz+r4OOmaiuWk4Rtd4arqdAzeHeprg==} engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} peerDependencies: '@babel/core': '*' - '@react-native/community-cli-plugin@0.83.6': - resolution: {integrity: sha512-Mko6mywoHYJmpBnjwAC95vQWaUUh//71knFadH0BrhHDq2m7i/IrpLwcQsPAy8855ucXflBs5zQyGTpNbPBAaw==} + '@react-native/community-cli-plugin@0.83.9': + resolution: {integrity: sha512-i8WhXYSEIB7kq99n6ZROI1lCckVEwz+Ti/oAT34UUU494MuSvZfd0UWEQ5cFpglPpIKq11KETPwmznHYF6xURg==} engines: {node: '>= 20.19.4'} peerDependencies: '@react-native-community/cli': '*' @@ -1861,52 +1853,36 @@ packages: '@react-native/metro-config': optional: true - '@react-native/community-cli-plugin@0.85.2': - resolution: {integrity: sha512-3KLgSg1kHvBpr93zMaQhvfYTgnCw7yZRED+3J4dMcYjfSjtD0Wf8SofU6uBmAw9JaVYvP43lpdwUpI4p0+ABsg==} - engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - peerDependencies: - '@react-native-community/cli': '*' - '@react-native/metro-config': 0.85.2 - peerDependenciesMeta: - '@react-native-community/cli': - optional: true - '@react-native/metro-config': - optional: true - '@react-native/debugger-frontend@0.83.6': resolution: {integrity: sha512-TyWXEpAjVundrc87fPWg91piOUg75+X9iutcfDe7cO3NrAEYCsl7Z09rKHuiAGkxfG9/rFD13dPsYIixUFkSFA==} engines: {node: '>= 20.19.4'} - '@react-native/debugger-frontend@0.85.2': - resolution: {integrity: sha512-j+0b9H5f5hGTLQxHIhJU/b/W6ijuxJF+ZTLHB0se2kzUBNxFKd7DkIc6753qk3CJdiv55vxG3XDgmlpbHxOpmA==} - engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/debugger-frontend@0.83.9': + resolution: {integrity: sha512-LEOJrHvVWnjHC+emTyybeHkrInrJBnchk3VEhW5qGMWhW3vSM421CrZSCKAvzy+fDg/viufwBfIJvwTdk066CA==} + engines: {node: '>= 20.19.4'} '@react-native/debugger-shell@0.83.6': resolution: {integrity: sha512-684TJMBCU0l0ZjJWzrnK0HH+ERaM9KLyxyArE1k7BrP+gVl4X9GO0Pi94RoInOxvW/nyV65sOU6Ip1F3ygS0cg==} engines: {node: '>= 20.19.4'} - '@react-native/debugger-shell@0.85.2': - resolution: {integrity: sha512-r5BkhqPMfg3LmaZS5zadHmBNVH5h4bhSpv4BEPGfK4gat9HABAMzUzybi+2wpgU3SoHxnyKGdExEJvoqVcjeRg==} - engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/debugger-shell@0.83.9': + resolution: {integrity: sha512-vAqDG7+3HOz4VOSfccSNe06C1+evcp2AbMjoYOmOyeJtt5dCdmGUp0FICsnU7iuctsNXWnHqyhks9M0wn7zEzA==} + engines: {node: '>= 20.19.4'} '@react-native/dev-middleware@0.83.6': resolution: {integrity: sha512-22xoddLTelpcVnF385SNH2hdP7X2av5pu7yRl/WnM5jBznbcl0+M9Ce94cj+WVeomsoUF/vlfuB0Ooy+RMlRiA==} engines: {node: '>= 20.19.4'} - '@react-native/dev-middleware@0.85.2': - resolution: {integrity: sha512-3J+NaDUg+QEfDeLAUzgaWhpaxEg78g+KwbydlDCewh2G6WnHpsty8XooruxNHzyAsqVWywZMrzmbn78Ctc1O9Q==} - engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - - '@react-native/gradle-plugin@0.83.6': - resolution: {integrity: sha512-5prXv7WWR1RgZ/kWGZP+mi7/y/IE2ymfOHIZO5Pv14tMOmRAcQSgSYogcRmOiWw5mJs2K0UFeMiQD49ZO9oCug==} + '@react-native/dev-middleware@0.83.9': + resolution: {integrity: sha512-4rW8KGkfqmZ5x1CTr+GrPBTdLG58UNkkJNQ8Dd9xdiVrZ/94sYKHm4HOo+UKTyOP0SNbZpZejDrf/2JNiqNL4Q==} engines: {node: '>= 20.19.4'} - '@react-native/gradle-plugin@0.85.2': - resolution: {integrity: sha512-YXBOLeAqFrv7XwUeBPTKZeOV1FIxn4AW7UAEitScf3ibC8bu8+6NpJu4HWgbNQHg7vDbbTZVbcOl8EwGxsSq2w==} - engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} + '@react-native/gradle-plugin@0.83.9': + resolution: {integrity: sha512-v+iag7ft+fBRP54GthOiR70qrp0azgvW8PhNg0KFYw52xin09LoC0rk96CY+Hj5M1oGoQovrk0bdji7ipH2pGg==} + engines: {node: '>= 20.19.4'} - '@react-native/js-polyfills@0.83.6': - resolution: {integrity: sha512-VSev0LV2i5X0ibduHBSLqKj0YU2F+waCgjl2uvaGHMGCSV1ZRKNFX/vJFqvLwjvdzLbkAZoFT1Rg7k7jDv44UA==} + '@react-native/js-polyfills@0.83.9': + resolution: {integrity: sha512-6+OdNC3GSgNI3L31zo730AxdI3eXjwD74k0M11gpvXKTOX/NQuXe3On+K4yLFokD2BFr8bL6XaqxCniCUQ3Q+Q==} engines: {node: '>= 20.19.4'} '@react-native/js-polyfills@0.85.2': @@ -1929,11 +1905,11 @@ packages: '@react-native/normalize-colors@0.83.6': resolution: {integrity: sha512-bTM24b5v4qN3h52oflnv+OujFORn/kVi06WaWhnQQw14/ycilPqIsqsa+DpIBqdBrXxvLa9fXtCRrQtGATZCEw==} - '@react-native/normalize-colors@0.85.2': - resolution: {integrity: sha512-svuOLtjbFGXDdHsriHXuND5FgHg7XlkOXCbH/8+X4t76YLH6qSTffSIQQrKLDL5mn4EFU+Oh/PNO0/FfpnTOTg==} + '@react-native/normalize-colors@0.83.9': + resolution: {integrity: sha512-GSXSvH+siDF2z+XtoF3Kk6zRFMWhGIpfq9nalBd4DSV97g5yNRQ5Htm2fpq7vBYNN+hVgvGZf7rRqnjn8bvfHg==} - '@react-native/virtualized-lists@0.83.6': - resolution: {integrity: sha512-gNSFXeb4P7qHtauLvl+zESroULIyX6Ltpvau3dhwy/QmfanBv0KUcrIU/7aVXxtWcXgp+54oWJyu2LIrsZ9+LQ==} + '@react-native/virtualized-lists@0.83.9': + resolution: {integrity: sha512-fZ84faVUANrBU7GfL1wPSkGkn5cnCPVXxAwutKit1S5i8hlfwCnt0gE9Fu/0fpAIeuGxHVed00w2xxDVXyLxKQ==} engines: {node: '>= 20.19.4'} peerDependencies: '@types/react': ^19.2.0 @@ -1943,19 +1919,8 @@ packages: '@types/react': optional: true - '@react-native/virtualized-lists@0.85.2': - resolution: {integrity: sha512-wmVKpAlcr+UB0L5SpbrV865EdleUP7I5+X+48e1aRsQK8q+wsTRBXeUwWVip/1l+HZwlZFeO8iOILJ16VRu0Cw==} - engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - peerDependencies: - '@types/react': ^19.2.0 - react: '*' - react-native: 0.85.2 - peerDependenciesMeta: - '@types/react': - optional: true - - '@react-navigation/bottom-tabs@7.15.10': - resolution: {integrity: sha512-Ao/yYlrpr0cwYYGxt9FDMQk+tTSHNm4WTaszyhroINLdoEMuKH19k1tGFdYbRBKHJx1UIH8kD+EZTYW1w6LL3Q==} + '@react-navigation/bottom-tabs@7.15.11': + resolution: {integrity: sha512-+WtNbd6fJgbViDNjmBUUP7eTgGH+zBtrl3jHuNnfUfXTs9YGuI5q3SiHIc9a5gY3voBOxbOXEiHJyW4xea7nAw==} peerDependencies: '@react-navigation/native': ^7.2.2 react: '>= 18.2.0' @@ -2025,8 +1990,8 @@ packages: jest: optional: true - '@tootallnate/once@2.0.0': - resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==} + '@tootallnate/once@2.0.1': + resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} engines: {node: '>= 10'} '@tsconfig/node18@18.2.6': @@ -2047,8 +2012,8 @@ packages: '@types/emscripten@1.41.5': resolution: {integrity: sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==} - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} '@types/graceful-fs@4.1.9': resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} @@ -2084,6 +2049,9 @@ packages: resolution: {integrity: sha512-6ZRPQrYM72qYKGWidEttRe6M5DZBEV5F+MHMHqd4TTYx0tfkcdrUFGdef6CCxY0jXU7wldvd/zA/b0A/kTeJmA==} deprecated: This is a stub types definition. react-native provides its own type definitions, so you do not need this installed. + '@types/react-test-renderer@19.1.0': + resolution: {integrity: sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==} + '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} @@ -2102,67 +2070,67 @@ packages: '@types/yargs@17.0.35': resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@typescript-eslint/eslint-plugin@8.59.1': - resolution: {integrity: sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==} + '@typescript-eslint/eslint-plugin@8.59.2': + resolution: {integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.59.1 + '@typescript-eslint/parser': ^8.59.2 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.1': - resolution: {integrity: sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==} + '@typescript-eslint/parser@8.59.2': + resolution: {integrity: sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==} 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.59.1': - resolution: {integrity: sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==} + '@typescript-eslint/project-service@8.59.2': + resolution: {integrity: sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.59.1': - resolution: {integrity: sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==} + '@typescript-eslint/scope-manager@8.59.2': + resolution: {integrity: sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.59.1': - resolution: {integrity: sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==} + '@typescript-eslint/tsconfig-utils@8.59.2': + resolution: {integrity: sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.1': - resolution: {integrity: sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==} + '@typescript-eslint/type-utils@8.59.2': + resolution: {integrity: sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==} 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.59.1': - resolution: {integrity: sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==} + '@typescript-eslint/types@8.59.2': + resolution: {integrity: sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.59.1': - resolution: {integrity: sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==} + '@typescript-eslint/typescript-estree@8.59.2': + resolution: {integrity: sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.1': - resolution: {integrity: sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==} + '@typescript-eslint/utils@8.59.2': + resolution: {integrity: sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==} 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.59.1': - resolution: {integrity: sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==} + '@typescript-eslint/visitor-keys@8.59.2': + resolution: {integrity: sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} '@xmldom/xmldom@0.8.13': resolution: {integrity: sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw==} @@ -2374,12 +2342,12 @@ packages: peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-expo@55.0.18: - resolution: {integrity: sha512-zmDwKxCFBTe4e/jQXuITRUZlbl8HTZOhsUlwcHGjwEUB0lKQfRdaSYXZckQ+jMOBC34MrOl3Cs7/6F6vNbj5Pw==} + babel-preset-expo@55.0.21: + resolution: {integrity: sha512-anXoUZBcxydLdVs2L+r3bWKGUvZv2FtgOl8xRJ12i/YfKICBpwTGZWSTiEYTqBByZ6GkA3mE9+3TW97X2ocFTQ==} peerDependencies: '@babel/runtime': ^7.20.0 expo: '*' - expo-widgets: ^55.0.14 + expo-widgets: ^55.0.17 react-refresh: '>=0.14.0 <1.0.0' peerDependenciesMeta: '@babel/runtime': @@ -2405,14 +2373,14 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - barcode-detector@3.1.2: - resolution: {integrity: sha512-Q5kjXpVH5I3ItykNzbWmfWnNryFN1ZTWp10k9/PKJuS0RnoKR7jTrHEJODR4fn04bRomq7TJwie/Dr9fj/GoGQ==} + barcode-detector@3.1.3: + resolution: {integrity: sha512-omL3/x26oU9jlR0gUQcGdXIjQtMlrUGKF7xRFO1RwrQkRkRU7WLz0mgQEsdUtYBm2uX3JH+HQLrKlyTS/BxZRw==} base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - baseline-browser-mapping@2.10.23: - resolution: {integrity: sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g==} + baseline-browser-mapping@2.10.27: + resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} engines: {node: '>=6.0.0'} hasBin: true @@ -2496,8 +2464,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001791: - resolution: {integrity: sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ==} + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -2531,9 +2499,6 @@ packages: chromium-edge-launcher@0.2.0: resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} - chromium-edge-launcher@0.3.0: - resolution: {integrity: sha512-p03azHlGjtyRvFEee3cyvtsRYdniSkwjkzmM/KmVnqT5d7QkkwpJBhis/zCLMYdQMVJ5tt140TBNqqrZPaWeFA==} - ci-info@2.0.0: resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} @@ -2806,8 +2771,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.344: - resolution: {integrity: sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg==} + electron-to-chromium@1.5.352: + resolution: {integrity: sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -2915,8 +2880,8 @@ packages: peerDependencies: eslint: '>=7.0.0' - eslint-config-universe@15.0.3: - resolution: {integrity: sha512-fUMsNXp7GJBu7Sz9PXFBbXhkiixdQ5sbnViFIBbk6ORAfeokczJ+eVv5HQ2gwxPQdbfJarpkO9WZDtxIvJnEGw==} + eslint-config-universe@15.0.4: + resolution: {integrity: sha512-7XTb/JTLzntJTUHXnR7ADl78kzRpQLm75NOjx1kYFnEMArJk69mDJ96WREzttro4/TOlQ9paGL+WFsRXk1vLkw==} peerDependencies: eslint: '>=8.10' prettier: '>=3' @@ -3092,8 +3057,8 @@ packages: peerDependencies: expo: '*' - expo-asset@55.0.16: - resolution: {integrity: sha512-5IJyfJtYqvKGg04NKGQWiCIoK/fULDL9m15mXPPyfabD1jsToVj2hnWmo1r2SWNNmMwtQxi6jTpcGwVo2nLDxg==} + expo-asset@55.0.17: + resolution: {integrity: sha512-pK9HHJuFqjE8kDUcbMFsZj3Cz8WdXpvZHZmYl7ouFQp59P83BvHln6VnqPDGlO+/4929G0Lm8ZUzbONuNRhi9w==} peerDependencies: expo: '*' react: '*' @@ -3104,8 +3069,8 @@ packages: peerDependencies: expo: '*' - expo-camera@55.0.16: - resolution: {integrity: sha512-9c6FGrLVwMLyQ08wqwkH7DXAC8Oj1VD0LXM4hFu62Nuq2f2zIAZwsXxG7J5ex+HHHAGyligGGi6VJWuiib9qNg==} + expo-camera@55.0.18: + resolution: {integrity: sha512-Us/7JV6O1lHpLBGKJnK2s8gzmPcmMVJSV5586DBeO7x7AXzmvvVGtH+0nJRVIBE3MNzGzGWyfgievjr8QlE7dA==} peerDependencies: expo: '*' react: '*' @@ -3115,8 +3080,15 @@ packages: react-native-web: optional: true - expo-constants@55.0.15: - resolution: {integrity: sha512-w394fcZLJjeKN+9ZnJzL/HiarE1nwZFDa+3S9frevh6Ur+MAAs9QDrcXhDrV8T3xqRzzYaqsP6Z8TFZ4efWN1A==} + expo-clipboard@55.0.13: + resolution: {integrity: sha512-PrOmmuVsGW4bAkNQmGKtxMXj3invsfN+jfIKmQxHwE/dn7ODqwFWviUTa+PMUjP3XZmYCDLyu/i0GLeu7HF9Ew==} + peerDependencies: + expo: '*' + react: '*' + react-native: '*' + + expo-constants@55.0.16: + resolution: {integrity: sha512-Z15/No94UHoogD+pulxjudGAeOHTEIWZgb/vnX48Wx5D+apWTeCbnKxQZZtGQlosvduYL5kaic2/W8U+NHfBQQ==} peerDependencies: expo: '*' react-native: '*' @@ -3126,21 +3098,21 @@ packages: peerDependencies: expo: '*' - expo-file-system@55.0.17: - resolution: {integrity: sha512-d27K1cagUOt2BwxwPka9KW8Znu5kN1tnairozCzzCRZviZFtWnBxwFuJ3KU6MAbav/9UhSMkp5Ve/oZ+SR0UgQ==} + expo-file-system@55.0.19: + resolution: {integrity: sha512-c4smCbMqELLI3YQrGpw21MwZIREXM2e53vQD/+KWQcae1q+hgw8J2TroEqcQ/jVOtFpZYVvyVfgu4HDKNEKmNw==} peerDependencies: expo: '*' react-native: '*' - expo-font@55.0.6: - resolution: {integrity: sha512-x9czUA3UQWjIwa0ZUEs/eWJNqB4mAue/m4ltESlNPLZhHL0nWWqIfsyHmklTLFH7mVfcHSJvew6k+pR2FE1zVw==} + expo-font@55.0.7: + resolution: {integrity: sha512-oH39Xb+3i6Y69b7YRP+P+5WLx7621t+ep/RAgLwJJYpTjs7CnSohUG+873rEtqsTAuQGi63ms7x9ZeHj1E9LYw==} peerDependencies: expo: '*' react: '*' react-native: '*' - expo-glass-effect@55.0.10: - resolution: {integrity: sha512-5kL/jATvgJWdrqPdxixrECJqD2l8cfQ4ALr1DK7qi9XkyI97ejXvUjB2VsfEePNy3Fg+/VwzA3n3L7Nv3tAPkw==} + expo-glass-effect@55.0.11: + resolution: {integrity: sha512-wqq7GUOqSkfoFJzreZvBG0jzjsq5c582m3glhWSjcmIuByxXXWp6j6GY6hyFuYKzpOXhbuvusVxGCQi0yWnp3g==} peerDependencies: expo: '*' react: '*' @@ -3151,8 +3123,8 @@ packages: peerDependencies: expo: '*' - expo-image@55.0.9: - resolution: {integrity: sha512-+NVgWv+tr7a6EpBEaIIVVp+XfruRA2JL5xOxvd6ajvFGdH0rOhagwX1m1piAII6w7sh6uAnBr8X+fDZsav7B2w==} + expo-image@55.0.10: + resolution: {integrity: sha512-We+vq/Z8jy8zmGxcOP8vrhiWkkwyXFdSks8cSlPi0bpu6D0Ei6l9Nj2xHWCD+yoENh92aCEe1+QRujAwXbogGA==} peerDependencies: expo: '*' react: '*' @@ -3162,14 +3134,14 @@ packages: react-native-web: optional: true - expo-keep-awake@55.0.6: - resolution: {integrity: sha512-acJjeHqkNxMVckEcJhGQeIksqqsarscSHJtT559bNgyiM4r14dViQ66su7bb6qDVeBt0K7z3glXI1dHVck1Zgg==} + expo-keep-awake@55.0.8: + resolution: {integrity: sha512-PfIpMfM+STOBwkR5XOE+yVtER86c44MD+W8QD8JxuO0sT9pF7Y1SJYakWlpvX8xsGA+bjKLxftm9403s9kQhKA==} peerDependencies: expo: '*' react: '*' - expo-linking@55.0.14: - resolution: {integrity: sha512-ZSqOvJyEquf04M5/ZpQo2diK9QRnNrzgqZo7p8gzxaPPHxP6IyUJnmcd12qT+dTxnRTVmUpxFQVHHWbvwPNIwQ==} + expo-linking@55.0.15: + resolution: {integrity: sha512-/RQh2vkNqV8Bim9Owm/evVqn2fqTvCDYHkpYPoSKbLAdydSGdHC2xZNw7Odl4wu1i1/3L4Xz//LKd3NsPWYWBQ==} peerDependencies: react: '*' react-native: '*' @@ -3178,12 +3150,12 @@ packages: resolution: {integrity: sha512-gtFQzs6wduZxYo4/1udcVLANDvQbVUVFjK1xJjgtZuUBlbJ+xFjQg5wOsQaffUDO3kqrG+DRXt1ihSxK72Wqkg==} hasBin: true - expo-modules-autolinking@55.0.18: - resolution: {integrity: sha512-olGTCWYkwVPj/momcgnF+z8MTzurGNFjopqPztQ4F53UkGPJnOFEuaM2/z4KbZtKbwHqeBv34OA5hxZP8uLdaQ==} + expo-modules-autolinking@55.0.21: + resolution: {integrity: sha512-P9KsJgOwI7JVwxmGfRvcXkXO4LNRvHRdWmb4ukLmX15G/vZ7b6SM17yiYkPceWq1F5KeeZ11KFjEcl0y17xy7w==} hasBin: true - expo-modules-core@55.0.23: - resolution: {integrity: sha512-IGWT5N9MoV4zgWyrv686bElnKhzhE7E6pSazhaBNh3vgViAah5nnAz2o5h5YoUMR2B+ZTdHumRbGHN6gHLgwPA==} + expo-modules-core@55.0.25: + resolution: {integrity: sha512-yXpfg7aHLbuqoXocK34Vua6Aey5SCyqLygAsXAMbul9P8vfBjLpaOPiTJ5cLVF7Drfq8ownqVJO6qpGEtZ6GOw==} peerDependencies: react: '*' react-native: '*' @@ -3192,23 +3164,23 @@ packages: react-native-worklets: optional: true - expo-notifications@55.0.21: - resolution: {integrity: sha512-nZKbvfjCoKFTZU435ihXcCCqCF3uCY1kcD5e2Tm2GMDLSt7HTiLZ1CQFGgckg/6QYIfMKhLFdHfOQHAEGakQug==} + expo-notifications@55.0.22: + resolution: {integrity: sha512-Rwvsp/lAEXfDYBxkQZpaLF9ZB25cJ/yfHhD/ESclbPesN0nbQBZ/5rGb1xS/saANtkStbEGfDlA80uHh2zEpsA==} peerDependencies: expo: '*' react: '*' react-native: '*' - expo-router@55.0.13: - resolution: {integrity: sha512-cIBR5RmQtbr+b535mlbMhmm7lweVZXFtjzJOgJTutoxIApRztl816kFRFNesnVyqQ0LZrEU0a6vqa3i0wdlRQw==} + expo-router@55.0.14: + resolution: {integrity: sha512-rOn/wosp2hAPM+O2o41hnarbP5Zqv9UkHWa31KoSoiOme1tpmZd2yc93XtRAtzP0P5E5xzqq7a2rbEAarpP5XA==} peerDependencies: - '@expo/log-box': 55.0.11 - '@expo/metro-runtime': ^55.0.10 + '@expo/log-box': 55.0.12 + '@expo/metro-runtime': ^55.0.11 '@react-navigation/drawer': ^7.9.4 '@testing-library/react-native': '>= 13.2.0' expo: '*' - expo-constants: ^55.0.15 - expo-linking: ^55.0.14 + expo-constants: ^55.0.16 + expo-linking: ^55.0.15 react: '*' react-dom: '*' react-native: '*' @@ -3234,31 +3206,36 @@ packages: react-server-dom-webpack: optional: true - expo-server@55.0.8: - resolution: {integrity: sha512-AoV5TKuO4biSzrhe/OVLyInfTT0pV9/OOc/g/oVq5vmCjL8SaSYTkES8PLt+67Tm7VqX+Dn0+kSx1nQcjEKaPw==} - engines: {node: '>=20.16.0'} - - expo-splash-screen@55.0.19: - resolution: {integrity: sha512-l8BWI/inLJW46Ojz5NgwvaM8LftrdXeFfZBUXhAoZxg44Qo2xKY76s0S1h3WIxWXT4sRKwK8YQzGr4k+zHubxQ==} + expo-secure-store@55.0.13: + resolution: {integrity: sha512-I6r0JNO1Fd4o0Gu7Ixiic7s89lqgdUHq17uBH9y1f/AntoyKn71TdtYJH82RgfsBbu5qNVzrwImmvlANyOlITQ==} peerDependencies: expo: '*' - expo-status-bar@55.0.5: - resolution: {integrity: sha512-qb0c3rJO2b7CC0gUVGi1JYp92oLenWdYGyk8l4YQs6U+uaXUTPv6aaFa3KkT2HON10re3AxxPNJci8rsz6kPxg==} + expo-server@55.0.9: + resolution: {integrity: sha512-N5Ipn1NwqaJzEm+G97o0Jbe4g/th3R/16N1DabnYryXKCiZwDkK13/w3VfGkQN9LOOaBP+JIRxGf4M8lQKPzyA==} + engines: {node: '>=20.16.0'} + + expo-splash-screen@55.0.20: + resolution: {integrity: sha512-WI5T0dutiZhxsqlF+jhEP4JRpQNILLlP8IpmKehsnV53Cncv6AQrKE7y1sOWwDyC2m2GBufZ/Vwam1RMt2EfmA==} + peerDependencies: + expo: '*' + + expo-status-bar@55.0.6: + resolution: {integrity: sha512-ijOUptfdiqYt7rObZ6jrPQ8sE5YN/8MxKCIJx0b7TY4nGkSJxhPIxeoW4GXcXCA8mTQ9PiOHH/ThLZgRVZvUlQ==} peerDependencies: react: '*' react-native: '*' - expo-symbols@55.0.7: - resolution: {integrity: sha512-y4ALLbncSGQzhFLw1PaIBbO39xzaw3ie249HmK6zK/WLJYfw4Z/9UU4iPKO3KCE4FyCKIzd+yRsvzvlri23YrQ==} + expo-symbols@55.0.8: + resolution: {integrity: sha512-Dg6BTu+fCWukdlh+3XYIr6NbqJWmK4aAQ6i6BInKnWU0ALuzVUJcMDq8Lk9bHok2hOh3OhzJqlCqEoBXPInIVQ==} peerDependencies: expo: '*' expo-font: '*' react: '*' react-native: '*' - expo@55.0.17: - resolution: {integrity: sha512-yVF2phiPw5XgOCedC/oQaL3j0XbwzsBLst3JiAF8bi9aFlxLOVvuDEM8BDg3E09XGSLaGCAclY4q5L+sFerXlQ==} + expo@55.0.23: + resolution: {integrity: sha512-b+lKwfzJzFiSm9G0wVGWw3c2YoZyubbl9gHOF1ZFuK8FqtxSge8pDDJMuEFmTi14dbKwh/tirB7MiORq54r7CQ==} hasBin: true peerDependencies: '@expo/dom-webview': '*' @@ -3504,9 +3481,6 @@ packages: hermes-compiler@0.14.1: resolution: {integrity: sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA==} - hermes-compiler@250829098.0.10: - resolution: {integrity: sha512-TcRlZ0/TlyfJqquRFAWoyElVNnkdYRi/sEp4/Qy8/GYxjg8j2cS9D4MjuaQ+qimkmLN7AmO+44IznRf06mAr0w==} - hermes-estree@0.32.0: resolution: {integrity: sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==} @@ -3649,8 +3623,8 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + is-core-module@2.16.2: + resolution: {integrity: sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==} engines: {node: '>= 0.4'} is-data-view@1.0.2: @@ -3850,8 +3824,8 @@ packages: resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jest-expo@55.0.16: - resolution: {integrity: sha512-bOvrTNyDaiaoTz9GhvnXib9v9rjX9PTJFvvoqRMRKEg4MoHghG82E7YF+pH71EWSXTaibQ07F46GS+fcUxTWEg==} + jest-expo@55.0.17: + resolution: {integrity: sha512-LEJJyPBYtr7FzL5DUvnPsWF4ial6h23XQ37ikmqu/HFR4KidFkQ03ht2jsUWUSqmN/KdKeWSesEmbe5AUNZn3A==} hasBin: true peerDependencies: expo: '*' @@ -4142,15 +4116,15 @@ packages: lru-cache@10.4.3: resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@11.3.5: - resolution: {integrity: sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==} + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} engines: {node: 20 || >=22} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lucide-react-native@1.11.0: - resolution: {integrity: sha512-/dEt+/noOpsvgH1dbMHHSv+f8+npB1BJSKCqK9v9yvHbssN3OJJJXrih6N1prJGG3vj0e1zKiVLtet+OKYmiXg==} + lucide-react-native@1.14.0: + resolution: {integrity: sha512-FkOjd4JHIB8SplakHQjeo4RR/5peXtl+PbL+TghQqWzcQ+AKbJ/PFl5xEnerLtSQ4EIq6B9uXblY7QSyKg05WQ==} peerDependencies: react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-native: '*' @@ -4193,119 +4167,119 @@ packages: merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - metro-babel-transformer@0.83.6: - resolution: {integrity: sha512-1AnuazBpzY3meRMr04WUw14kRBkV0W3Ez+AA75FAeNpRyWNN5S3M3PHLUbZw7IXq7ZeOzceyRsHStaFrnWd+8w==} + metro-babel-transformer@0.83.7: + resolution: {integrity: sha512-sBqBkt6kNut/88bv+Ucvm4yqdPetbvAEsHzi3MAgJEifOSYYzX5Z5Kgw3TFOrwf/mHJTOBG2ONlaMHoyfP15TA==} engines: {node: '>=20.19.4'} - metro-babel-transformer@0.84.3: - resolution: {integrity: sha512-svAA+yMLpeMiGcz/jKJs4oHpIGEx4nBqNEJ5AGj4CYIg1efvK+A0TjR6tgIuc6tKO5e8JmN/1lglpN2+f3/z/w==} + 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.83.6: - resolution: {integrity: sha512-5gdK4PVpgNOHi7xCGrgesNP1AuOA2TiPqpcirGXZi4RLLzX1VMowpkgTVtBfpQQCqWoosQF9yrSo9/KDQg1eBg==} + metro-cache-key@0.83.7: + resolution: {integrity: sha512-W1c2Nmx8MiJTJt+eWhMO08z9VKi3kZOaz99IYGdqeqDgY9j+yZjXl62rUav4Di0heZfh4/n2s722PqRL1OODeg==} engines: {node: '>=20.19.4'} - metro-cache-key@0.84.3: - resolution: {integrity: sha512-TnSL1Fdvrw+2glTdBSRmA5TL8l/i16ECjsrUdf3E5HncA+sNx8KcwDG8r+3ct1UhfYcusJypzZqTN55FZZcwGg==} + 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.83.6: - resolution: {integrity: sha512-DpvZE32feNkqfZkI4Fic7YI/Kw8QP9wdl1rC4YKPrA77wQbI9vXbxjmfkCT/EGwBTFOPKqvIXo+H3BNe93YyiQ==} + metro-cache@0.83.7: + resolution: {integrity: sha512-E9SRePXQ1Zvlj79VcOk57q7VC7rMHMFQ+jhmPHBiq+dJ0bJB5BL87lWZF6oh5X76Cci5tpDuQNaDwwuSCToEeg==} engines: {node: '>=20.19.4'} - metro-cache@0.84.3: - resolution: {integrity: sha512-0QElxwLaHqLZf+Xqio8QrjVbuXP/8sJfQBGSPiITlKDVXrVLefuzYVSH9Sj+QL6lrPj2gYZd/iwQh1yZuVKnLA==} + 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.83.6: - resolution: {integrity: sha512-G5622400uNtnAMlppEA5zkFAZltEf7DSGhOu09BkisCxOlVMWfdosD/oPyh4f2YVQsc1MBYyp4w6OzbExTYarg==} + metro-config@0.83.7: + resolution: {integrity: sha512-83mjWFbFOt2GeJ6pFIum5mSnc1uTsZJAtD8o4ej0s4NVsYsA7fB+pHvTfHhFrpeMONaobu2riKavkPei05Er/Q==} engines: {node: '>=20.19.4'} - metro-config@0.84.3: - resolution: {integrity: sha512-JmCzZWOETR+O22q8oPBWyQppx3roU9EbkbGzD8Gf1jukQ4b5T1fTzqqHruu6K4sTiNq5zVQySmKF6bp4kVARew==} + 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.83.6: - resolution: {integrity: sha512-l+yQ2fuIgR//wszUlMrrAa9+Z+kbKazd0QOh0VQY7jC4ghb7yZBBSla/UMYRBZZ6fPg9IM+wD3+h+37a5f9etw==} + metro-core@0.83.7: + resolution: {integrity: sha512-6yn3w1wnltT6RQl7p7YES2l95ArC+mWrOssEiH8p5/DDrJS65/szf9LsC9JrBv8c5DdvSY3V3f0GRYg0Ox7hCg==} engines: {node: '>=20.19.4'} - metro-core@0.84.3: - resolution: {integrity: sha512-cc0pvAa80ai1nDmqqz0P59a+0ZqCZ/YHU/3jEekZL6spFnYDfX8iDLdn9FR6kX+67rmzKxHNrbrSRFLX2AYocw==} + 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.83.6: - resolution: {integrity: sha512-Jg3oN604C7GWbQwFAUXt8KsbMXeKfsxbZ5HFy4XFM3ggTS+ja9QgUmq9B613kgXv3G4M6rwiI6cvh9TRly4x3w==} + metro-file-map@0.83.7: + resolution: {integrity: sha512-+j0F1m+FQYVAQ6syf+mwhIPV5GoFQrkInX8bppuc50IzNsZbMrp8R5H/Sx/K2daQ3YEa9F/XwkeZT8gzJfgeCw==} engines: {node: '>=20.19.4'} - metro-file-map@0.84.3: - resolution: {integrity: sha512-1cL4m4Jv1yRUt9RJExZQLfccscdlMNOcRG6LHLtmJhf3BG9j3MujPVc7CIpKYdFl+KUl+sdjge6oO3+meKCHQA==} + 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.83.6: - resolution: {integrity: sha512-Vx3/Ne9Q+EIEDLfKzZUOtn/rxSNa/QjlYxc42nvK4Mg8mB6XUgd3LXX5ZZVq7lzQgehgEqLrbgShJPGfeF8PnQ==} + metro-minify-terser@0.83.7: + resolution: {integrity: sha512-MfJar2IS4tBRuLb9svwb0Gu5l9BsH+pcRm8eGcEi/wy8MzZinfinh5dFLt2nWkocnulIgtGB5NkFDdbXqMXKhQ==} engines: {node: '>=20.19.4'} - metro-minify-terser@0.84.3: - resolution: {integrity: sha512-3ofrG2OQyJbO9RNhCfOcl8QU7EE2WrSsnN5dFkuZaJO5+4Imujr9bUXmspeNlXRsOVk0F/rVRbEFH98lFSCkBQ==} + 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.83.6: - resolution: {integrity: sha512-lAwR/FsT1uJ5iCt4AIsN3boKfJ88aN8bjvDT5FwBS0tKeKw4/sbdSTWlFxc7W/MUTN5RekJ3nQkJRIWsvs28tA==} + metro-resolver@0.83.7: + resolution: {integrity: sha512-WSJIENlMcoSsuz66IfBHOkgfp3KJt2UW2TnEHPf1b8pIG2eEXNOVmo2+03A0H17WY2XGXWgxL0CG7FAopqgB1A==} engines: {node: '>=20.19.4'} - metro-resolver@0.84.3: - resolution: {integrity: sha512-pjEzGDtoM8DTHAIPK/9u9ZxszEiuRohYUVImWvgbnB91V4gqYJpQcoEYUugf2NIm1lrX5HNu0OvNqWmPBnGYjA==} + 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.83.6: - resolution: {integrity: sha512-WQPua1G2VgYbwRn6vSKxOhTX7CFbSf/JdUu6Nd8bZnPXckOf7HQ2y51NXNQHoEsiuawathrkzL8pBhv+zgZFmg==} + metro-runtime@0.83.7: + resolution: {integrity: sha512-9GKkJURaB2iyYoEExKnedzAHzxmKtSi+k0tsZUvMoU27tBZJElchYt7JH/Ai/XzYAI9lCAaV7u5HZSI8J5Z+wQ==} engines: {node: '>=20.19.4'} - metro-runtime@0.84.3: - resolution: {integrity: sha512-o7HLRfMyVk9N2dUZ9VjQfB6xxUItL9Pi9WcqxURE7MEKOH6wbGt9/E92YdYLluTOtkzYAEVfdC6h6lcxqA+hMQ==} + 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.83.6: - resolution: {integrity: sha512-AqJbOMMpeyyM4iNI91pchqDIszzNuuHApEhg6OABqZ+9mjLEqzcIEQ/fboZ7x74fNU5DBd2K36FdUQYPqlGClA==} + metro-source-map@0.83.7: + resolution: {integrity: sha512-JgA1h7oc1a1jydBe1GhVFsUoMYo3wLPk7oRA32rjlDsq+sP2JLt9x2p2lWbNSxTm/u8NV4VRid3hvEJgcX8tKw==} engines: {node: '>=20.19.4'} - metro-source-map@0.84.3: - resolution: {integrity: sha512-jS48CeSzw78M8y6VE0f9uy3lVmfbOS677j2VCxnlmlYmnahcXuC6IhoN9K6LynNvos9517yUadcfgioju38xYQ==} + 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.83.6: - resolution: {integrity: sha512-4nvkmv9T7ozhprlPwk/+xm0SVPsxly5kYyMHdNaOlFemFz4df9BanvD46Ac6OISu/4Idinzfk2KVb++6OfzPAQ==} + metro-symbolicate@0.83.7: + resolution: {integrity: sha512-g4suyxw20WOHWI680c+Kq4wC/NF+Hx5pRH9afrMp+sMTxqLeKcPR1Xf4wMhsjlbvx7LbIREdke6q928jEjvJWw==} engines: {node: '>=20.19.4'} hasBin: true - metro-symbolicate@0.84.3: - resolution: {integrity: sha512-J9Tpo8NCycYrozRvBIUyOwGAu4xkawOsAppmTscFiaegK0WvuDGwIM53GbzVSnytCHjVAF0io5GQxpkrKTuc7g==} + 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.83.6: - resolution: {integrity: sha512-V+zoY2Ul0v0BW6IokJkTud3raXmDdbdwkUQ/5eiSoy0jKuKMhrDjdH+H5buCS5iiJdNbykOn69Eip+Sqymkodg==} + metro-transform-plugins@0.83.7: + resolution: {integrity: sha512-Ss0FpBiZDjX2kwhukMDl5sNdYK8T/06IPqxNE4H6PTlRlfs9q11cef13c/xESY/Pm4VCkp1yJUZO3kXzvMxQFA==} engines: {node: '>=20.19.4'} - metro-transform-plugins@0.84.3: - resolution: {integrity: sha512-8S3baq2XhBaafHEH5Q8sJW6tmzsEJk80qKc3RU/nZV1MsnYq94RdjTUR6AyKjQd6Rfsk1BtBxhtiNnk7mgslCg==} + 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.83.6: - resolution: {integrity: sha512-G5kDJ/P0ZTIf57t3iyAd5qIXbj2Wb1j7WtIDh82uTFQHe2Mq2SO9aXG9j1wI+kxZlIe58Z22XEXIKMl89z0ibQ==} + metro-transform-worker@0.83.7: + resolution: {integrity: sha512-UegCo7ygB2fT64mRK2nbAjQVJ1zSwIIHy8d96jJv2nKZFDaViYBiughEdu5HM/Ceq0WN3LZrZk3zhl9aoiLYFw==} engines: {node: '>=20.19.4'} - metro-transform-worker@0.84.3: - resolution: {integrity: sha512-Wjba7PyYktNRsHbPmkx2J2UX32rAzcDXjCu49zPHeF/viJlYJhwRaNePQcHaCRqQ+kmgQT4ThprsnJfDj71ZMA==} + 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.83.6: - resolution: {integrity: sha512-pbdndsAZ2F/ceopDdhVbttpa/hfLzXPJ/husc+QvQ33R0D9UXJKzTn5+OzOXx4bpQNtAKF2bY88cCI3Zl44xDQ==} + metro@0.83.7: + resolution: {integrity: sha512-SPaPEyvTsTmd0LpT7RaZciQyDw2i/JB7+iY9L5VfBo72+psescFxBqpI1TL9dnL+pmnfkU+l/J1mEEGLeF65EQ==} engines: {node: '>=20.19.4'} hasBin: true - metro@0.84.3: - resolution: {integrity: sha512-1h3lbVrE6hGf1e/764HfhPGg/bGrWMJDDh7G2rc4gFYZboVuI40BlG/y+UhtbhQDNlO/csMvrcnK0YrTlHUVew==} + 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 @@ -4374,8 +4348,8 @@ packages: multitars@1.0.0: resolution: {integrity: sha512-H/J4fMLedtudftaYMOg7ajzLYgT3/rwbWVJbqr/iUgB8DQztn38ys5HOqI1CzSxx8QhXXwOOnnBvd4v3jG5+Mg==} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -4438,12 +4412,12 @@ packages: nwsapi@2.2.23: resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} - ob1@0.83.6: - resolution: {integrity: sha512-m/xZYkwcjo6UqLMrUICEB3iHk7Bjt3RSR7KXMi6Y1MO/kGkPhoRmfUDF6KAan3rLAZ7ABRqnQyKUTwaqZgUV4w==} + ob1@0.83.7: + resolution: {integrity: sha512-9M5kpuOLyTPogMtZiQUIxdAZxl7Dxs6tVBbJErSumsqGMuhVSoUbkfeZ3XNPpLpwBBtqY5QDUzGwggLHX3slQg==} engines: {node: '>=20.19.4'} - ob1@0.84.3: - resolution: {integrity: sha512-J7554Ef8bzmKaDY365Afq6PF+qtdnY/d5PKUQFrsKlZHV/N3OGZewVrvDrQDyX5V5NJjTpcAKtlrFZcDr+HvpQ==} + 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: @@ -4526,12 +4500,12 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - oxlint@1.62.0: - resolution: {integrity: sha512-1uFkg6HakjsGIpW9wNdeW4/2LOHW9MEkoWjZUTUfQtIHyLIZPYt00w3Sg+H3lH+206FgBPHBbW5dVE5l2ExECQ==} + oxlint@1.63.0: + resolution: {integrity: sha512-9TGXetdjgIHOJ9OiReomP7nnrMkV9HxC1xM2ramJSLQpzxjsAJtQwa4wqkJN2f/uCrqZuJseFuSlWDdvcruveg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: - oxlint-tsgolint: '>=0.18.0' + oxlint-tsgolint: '>=0.22.1' peerDependenciesMeta: oxlint-tsgolint: optional: true @@ -4725,17 +4699,11 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-is@19.2.5: - resolution: {integrity: sha512-Dn0t8IQhCmeIT3wu+Apm1/YVsJXsGWi6k4sPdnBIdqMVtHtv0IGi6dcpNpNkNac0zB2uUAqNX3MHzN8c+z2rwQ==} + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} - react-native-gesture-handler@2.30.1: - resolution: {integrity: sha512-xIUBDo5ktmJs++0fZlavQNvDEE4PsihWhSeJsJtoz4Q6p0MiTM9TgrTgfEgzRR36qGPytFoeq+ShLrVwGdpUdA==} - peerDependencies: - react: '*' - react-native: '*' - - react-native-is-edge-to-edge@1.2.1: - resolution: {integrity: sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==} + react-native-gesture-handler@2.31.2: + resolution: {integrity: sha512-rw5q74i2AfS7YGYdbxQDhOU7xqgY6WRM1132/CCm3erqjblhECZDZFHIm0tteHoC9ih24wogVBVVzcTBQtZ+5A==} peerDependencies: react: '*' react-native: '*' @@ -4746,21 +4714,21 @@ packages: react: '*' react-native: '*' - react-native-reanimated@4.2.1: - resolution: {integrity: sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==} + react-native-reanimated@4.3.0: + resolution: {integrity: sha512-HOTTPdKtddXTOsmQxDASXEwLS3lqEHrKERD3XOgzSqWJ7L3x81Pnx7mTcKx1FKdkgomMug/XSmm1C6Z7GIowxA==} peerDependencies: react: '*' - react-native: '*' - react-native-worklets: '>=0.7.0' + react-native: 0.81 - 0.85 + react-native-worklets: 0.8.x - react-native-safe-area-context@5.6.2: - resolution: {integrity: sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==} + 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.23.0: - resolution: {integrity: sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw==} + react-native-screens@4.24.0: + resolution: {integrity: sha512-SyoiGaDofiyGPFrUkn1oGsAzkRuX1JUvTD9YQQK3G1JGQ5VWkvHgYSsc1K9OrLsDQxN7NmV71O0sHCAh8cBetA==} peerDependencies: react: '*' react-native: '*' @@ -4783,15 +4751,16 @@ packages: react: '*' react-native: '*' - react-native-worklets@0.7.4: - resolution: {integrity: sha512-NYOdM1MwBb3n+AtMqy1tFy3Mn8DliQtd8sbzAVRf9Gc+uvQ0zRfxN7dS8ZzoyX7t6cyQL5THuGhlnX+iFlQTag==} + react-native-worklets@0.8.3: + resolution: {integrity: sha512-oCBJROyLU7yG/1R8s0INMflygTH71bx+5XcYkH0CM938TlhSoVbiunE1WVW5FZa51vwYqfLie/IXMX2s1Kh3eg==} peerDependencies: '@babel/core': '*' + '@react-native/metro-config': '*' react: '*' - react-native: '*' + react-native: 0.81 - 0.85 - react-native@0.83.6: - resolution: {integrity: sha512-H513+8VzviNFXOdPnStRzX9S3/jiJGg++QZ1zd+ROyAvBEKqFqKUPHH0d82y3QyRPct5qKjdOa7J6vNehCvXYA==} + react-native@0.83.9: + resolution: {integrity: sha512-bTQpaSJBbxOizI8SMNiCR/5aIvfO3ZlXAcWYxg6FmdbfJYCZ0zdUCK9mSZ4patureITGHNU94RXhxDAJhqsO7w==} engines: {node: '>= 20.19.4'} hasBin: true peerDependencies: @@ -4801,20 +4770,6 @@ packages: '@types/react': optional: true - react-native@0.85.2: - resolution: {integrity: sha512-GFWEPwLYirfj5X8gMtXOWtqX0cqUEURRHETZfFk37VCa4++izrKvGvv24anvuyulXV87NAhVkfNw93rLg3HByw==} - engines: {node: ^20.19.4 || ^22.13.0 || ^24.3.0 || >= 25.0.0} - hasBin: true - peerDependencies: - '@react-native/jest-preset': 0.85.2 - '@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'} @@ -4854,8 +4809,8 @@ packages: peerDependencies: react: ^19.2.0 - react@19.2.0: - resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==} + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} engines: {node: '>=0.10.0'} readdirp@3.6.0: @@ -4989,11 +4944,6 @@ packages: engines: {node: '>=10'} hasBin: true - semver@7.7.3: - resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} - engines: {node: '>=10'} - hasBin: true - semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -5487,6 +5437,7 @@ packages: 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 v8-to-istanbul@9.3.0: @@ -5642,8 +5593,8 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.8.3: - resolution: {integrity: sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==} + yaml@2.8.4: + resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==} engines: {node: '>= 14.6'} hasBin: true @@ -5662,11 +5613,11 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zod@4.3.6: - resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} - zustand@5.0.12: - resolution: {integrity: sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==} + zustand@5.0.13: + resolution: {integrity: sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -5683,8 +5634,8 @@ packages: use-sync-external-store: optional: true - zxing-wasm@3.0.2: - resolution: {integrity: sha512-2YMAriaYHX9wrBY2k7H0epSo+dyCaCZg/vOtt+nEDXM9ul480gkXz/9SkwpOeHcD2H5qqDG8lWDSBwpTcZpa6w==} + zxing-wasm@3.0.3: + resolution: {integrity: sha512-DdOn/G5F+qvZELWeO5ZFFwcN611TfMybxPV0LUUoutUmiH2t47MZSB7gLV9O9YLhvudBdnzQNAoFOu4Xz8eOrQ==} peerDependencies: '@types/emscripten': '>=1.39.6' @@ -5710,7 +5661,7 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.29.0': {} + '@babel/compat-data@7.29.3': {} '@babel/core@7.29.0': dependencies: @@ -5719,7 +5670,7 @@ snapshots: '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helpers': 7.29.2 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 @@ -5734,7 +5685,7 @@ snapshots: '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 @@ -5746,13 +5697,13 @@ snapshots: '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + '@babel/helper-create-class-features-plugin@7.29.3(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 @@ -5858,7 +5809,7 @@ snapshots: '@babel/template': 7.28.6 '@babel/types': 7.29.0 - '@babel/parser@7.29.2': + '@babel/parser@7.29.3': dependencies: '@babel/types': 7.29.0 @@ -5880,6 +5831,14 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array@7.29.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -5900,7 +5859,7 @@ snapshots: '@babel/plugin-proposal-decorators@7.29.0(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: @@ -6064,18 +6023,10 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -6083,23 +6034,11 @@ snapshots: '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.28.4(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-globals': 7.28.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -6225,7 +6164,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': + '@babel/plugin-transform-modules-systemjs@7.29.4(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) @@ -6254,11 +6193,6 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -6293,14 +6227,6 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - transitivePeerDependencies: - - supports-color - '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -6317,7 +6243,7 @@ snapshots: '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -6326,7 +6252,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color @@ -6435,7 +6361,7 @@ snapshots: dependencies: '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-create-class-features-plugin': 7.29.3(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) @@ -6465,9 +6391,9 @@ snapshots: '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 - '@babel/preset-env@7.29.2(@babel/core@7.29.0)': + '@babel/preset-env@7.29.5(@babel/core@7.29.0)': dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/core': 7.29.0 '@babel/helper-compilation-targets': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 @@ -6475,6 +6401,7 @@ snapshots: '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-rest-destructuring-rhs-array': 7.29.3(@babel/core@7.29.0) '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) @@ -6506,7 +6433,7 @@ snapshots: '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.4(@babel/core@7.29.0) '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) @@ -6560,17 +6487,6 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.27.1(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 @@ -6587,7 +6503,7 @@ snapshots: '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@babel/traverse@7.29.0': @@ -6595,7 +6511,7 @@ snapshots: '@babel/code-frame': 7.29.0 '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/types': 7.29.0 debug: 4.4.3 @@ -6739,28 +6655,28 @@ snapshots: '@expo-google-fonts/material-symbols@0.4.34': {} - '@expo/cli@55.0.26(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-constants@55.0.15)(expo-font@55.0.6)(expo-router@55.0.13)(expo@55.0.17)(react-dom@19.2.5(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3)': + '@expo/cli@55.0.29(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-constants@55.0.16)(expo-font@55.0.7)(expo-router@55.0.14)(expo@55.0.23)(react-dom@19.2.5(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3)': dependencies: '@expo/code-signing-certificates': 0.0.6 - '@expo/config': 55.0.15(typescript@5.9.3) + '@expo/config': 55.0.16(typescript@5.9.3) '@expo/config-plugins': 55.0.8 '@expo/devcert': 1.2.1 - '@expo/env': 2.1.1 - '@expo/image-utils': 0.8.13(typescript@5.9.3) - '@expo/json-file': 10.0.13 - '@expo/log-box': 55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@expo/metro': 55.1.0 - '@expo/metro-config': 55.0.17(expo@55.0.17)(typescript@5.9.3) - '@expo/osascript': 2.4.2 - '@expo/package-manager': 1.10.4 - '@expo/plist': 0.5.2 - '@expo/prebuild-config': 55.0.16(expo@55.0.17)(typescript@5.9.3) - '@expo/require-utils': 55.0.4(typescript@5.9.3) - '@expo/router-server': 55.0.15(@expo/metro-runtime@55.0.10)(expo-constants@55.0.15)(expo-font@55.0.6)(expo-router@55.0.13)(expo-server@55.0.8)(expo@55.0.17)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@expo/schema-utils': 55.0.3 + '@expo/env': 2.1.2 + '@expo/image-utils': 0.8.14(typescript@5.9.3) + '@expo/json-file': 10.0.14 + '@expo/log-box': 55.0.12(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@expo/metro': 55.1.1 + '@expo/metro-config': 55.0.20(expo@55.0.23)(typescript@5.9.3) + '@expo/osascript': 2.4.3 + '@expo/package-manager': 1.10.5 + '@expo/plist': 0.5.3 + '@expo/prebuild-config': 55.0.17(expo@55.0.23)(typescript@5.9.3) + '@expo/require-utils': 55.0.5(typescript@5.9.3) + '@expo/router-server': 55.0.16(@expo/metro-runtime@55.0.10)(expo-constants@55.0.16)(expo-font@55.0.7)(expo-router@55.0.14)(expo-server@55.0.9)(expo@55.0.23)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@expo/schema-utils': 55.0.4 '@expo/spawn-async': 1.7.2 '@expo/ws-tunnel': 1.0.6 - '@expo/xcpretty': 4.4.3 + '@expo/xcpretty': 4.4.4 '@react-native/dev-middleware': 0.83.6 accepts: 1.3.8 arg: 5.0.2 @@ -6773,8 +6689,8 @@ snapshots: connect: 3.7.0 debug: 4.4.3 dnssd-advertise: 1.1.4 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-server: 55.0.8 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + expo-server: 55.0.9 fetch-nodeshim: 0.4.10 getenv: 2.0.0 glob: 13.0.6 @@ -6800,8 +6716,8 @@ snapshots: ws: 8.20.0 zod: 3.25.76 optionalDependencies: - expo-router: 55.0.13(121105c3b042d5e83ab3c1d3b84ed55f) - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo-router: 55.0.14(021822249f1df9b3c5f483d3fca100f4) + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) transitivePeerDependencies: - '@expo/dom-webview' - '@expo/metro-runtime' @@ -6822,8 +6738,8 @@ snapshots: '@expo/config-plugins@55.0.8': dependencies: '@expo/config-types': 55.0.5 - '@expo/json-file': 10.0.13 - '@expo/plist': 0.5.2 + '@expo/json-file': 10.0.14 + '@expo/plist': 0.5.3 '@expo/sdk-runtime-versions': 1.0.0 chalk: 4.1.2 debug: 4.4.3 @@ -6839,12 +6755,12 @@ snapshots: '@expo/config-types@55.0.5': {} - '@expo/config@55.0.15(typescript@5.9.3)': + '@expo/config@55.0.16(typescript@5.9.3)': dependencies: '@expo/config-plugins': 55.0.8 '@expo/config-types': 55.0.5 - '@expo/json-file': 10.0.13 - '@expo/require-utils': 55.0.4(typescript@5.9.3) + '@expo/json-file': 10.0.14 + '@expo/require-utils': 55.0.5(typescript@5.9.3) deepmerge: 4.3.1 getenv: 2.0.0 glob: 13.0.6 @@ -6862,20 +6778,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/devtools@55.0.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/devtools@55.0.3(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: chalk: 4.1.2 optionalDependencies: - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - '@expo/dom-webview@55.0.5(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/dom-webview@55.0.5(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - '@expo/env@2.1.1': + '@expo/env@2.1.2': dependencies: chalk: 4.1.2 debug: 4.4.3 @@ -6883,9 +6799,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/fingerprint@0.16.6': + '@expo/fingerprint@0.16.7': dependencies: - '@expo/env': 2.1.1 + '@expo/env': 2.1.2 '@expo/spawn-async': 1.7.2 arg: 5.0.2 chalk: 4.1.2 @@ -6899,9 +6815,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/image-utils@0.8.13(typescript@5.9.3)': + '@expo/image-utils@0.8.14(typescript@5.9.3)': dependencies: - '@expo/require-utils': 55.0.4(typescript@5.9.3) + '@expo/require-utils': 55.0.5(typescript@5.9.3) '@expo/spawn-async': 1.7.2 chalk: 4.1.2 getenv: 2.0.0 @@ -6912,37 +6828,46 @@ snapshots: - supports-color - typescript - '@expo/json-file@10.0.13': + '@expo/json-file@10.0.14': dependencies: '@babel/code-frame': 7.29.0 json5: 2.2.3 - '@expo/local-build-cache-provider@55.0.11(typescript@5.9.3)': + '@expo/local-build-cache-provider@55.0.12(typescript@5.9.3)': dependencies: - '@expo/config': 55.0.15(typescript@5.9.3) + '@expo/config': 55.0.16(typescript@5.9.3) chalk: 4.1.2 transitivePeerDependencies: - supports-color - typescript - '@expo/log-box@55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/log-box@55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: - '@expo/dom-webview': 55.0.5(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/dom-webview': 55.0.5(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) anser: 1.4.10 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) stacktrace-parser: 0.1.11 - '@expo/metro-config@55.0.17(expo@55.0.17)(typescript@5.9.3)': + '@expo/log-box@55.0.12(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': + dependencies: + '@expo/dom-webview': 55.0.5(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + anser: 1.4.10 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + stacktrace-parser: 0.1.11 + + '@expo/metro-config@55.0.20(expo@55.0.23)(typescript@5.9.3)': dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@expo/config': 55.0.15(typescript@5.9.3) - '@expo/env': 2.1.1 - '@expo/json-file': 10.0.13 - '@expo/metro': 55.1.0 + '@expo/config': 55.0.16(typescript@5.9.3) + '@expo/env': 2.1.2 + '@expo/json-file': 10.0.14 + '@expo/metro': 55.1.1 '@expo/spawn-async': 1.7.2 browserslist: 4.28.2 chalk: 4.1.2 @@ -6956,44 +6881,44 @@ snapshots: postcss: 8.4.49 resolve-from: 5.0.0 optionalDependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) transitivePeerDependencies: - bufferutil - supports-color - typescript - utf-8-validate - '@expo/metro-runtime@55.0.10(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-dom@19.2.5(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/metro-runtime@55.0.10(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-dom@19.2.5(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: - '@expo/log-box': 55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/log-box': 55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) anser: 1.4.10 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) pretty-format: 29.7.0 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) stacktrace-parser: 0.1.11 whatwg-fetch: 3.6.20 optionalDependencies: - react-dom: 19.2.5(react@19.2.0) + react-dom: 19.2.5(react@19.2.6) transitivePeerDependencies: - '@expo/dom-webview' - '@expo/metro@55.1.0': + '@expo/metro@55.1.1': dependencies: - metro: 0.83.6 - metro-babel-transformer: 0.83.6 - metro-cache: 0.83.6 - metro-cache-key: 0.83.6 - metro-config: 0.83.6 - metro-core: 0.83.6 - metro-file-map: 0.83.6 - metro-minify-terser: 0.83.6 - metro-resolver: 0.83.6 - metro-runtime: 0.83.6 - metro-source-map: 0.83.6 - metro-symbolicate: 0.83.6 - metro-transform-plugins: 0.83.6 - metro-transform-worker: 0.83.6 + metro: 0.83.7 + metro-babel-transformer: 0.83.7 + metro-cache: 0.83.7 + metro-cache-key: 0.83.7 + metro-config: 0.83.7 + metro-core: 0.83.7 + metro-file-map: 0.83.7 + metro-minify-terser: 0.83.7 + metro-resolver: 0.83.7 + metro-runtime: 0.83.7 + metro-source-map: 0.83.7 + metro-symbolicate: 0.83.7 + metro-transform-plugins: 0.83.7 + metro-transform-worker: 0.83.7 transitivePeerDependencies: - bufferutil - supports-color @@ -7003,35 +6928,35 @@ snapshots: dependencies: semver: 5.7.2 - '@expo/osascript@2.4.2': + '@expo/osascript@2.4.3': dependencies: '@expo/spawn-async': 1.7.2 - '@expo/package-manager@1.10.4': + '@expo/package-manager@1.10.5': dependencies: - '@expo/json-file': 10.0.13 + '@expo/json-file': 10.0.14 '@expo/spawn-async': 1.7.2 chalk: 4.1.2 npm-package-arg: 11.0.3 ora: 3.4.0 resolve-workspace-root: 2.0.1 - '@expo/plist@0.5.2': + '@expo/plist@0.5.3': dependencies: '@xmldom/xmldom': 0.8.13 base64-js: 1.5.1 xmlbuilder: 15.1.1 - '@expo/prebuild-config@55.0.16(expo@55.0.17)(typescript@5.9.3)': + '@expo/prebuild-config@55.0.17(expo@55.0.23)(typescript@5.9.3)': dependencies: - '@expo/config': 55.0.15(typescript@5.9.3) + '@expo/config': 55.0.16(typescript@5.9.3) '@expo/config-plugins': 55.0.8 '@expo/config-types': 55.0.5 - '@expo/image-utils': 0.8.13(typescript@5.9.3) - '@expo/json-file': 10.0.13 + '@expo/image-utils': 0.8.14(typescript@5.9.3) + '@expo/json-file': 10.0.14 '@react-native/normalize-colors': 0.83.6 debug: 4.4.3 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) resolve-from: 5.0.0 semver: 7.7.4 xml2js: 0.6.0 @@ -7039,7 +6964,7 @@ snapshots: - supports-color - typescript - '@expo/require-utils@55.0.4(typescript@5.9.3)': + '@expo/require-utils@55.0.5(typescript@5.9.3)': dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 @@ -7049,22 +6974,22 @@ snapshots: transitivePeerDependencies: - supports-color - '@expo/router-server@55.0.15(@expo/metro-runtime@55.0.10)(expo-constants@55.0.15)(expo-font@55.0.6)(expo-router@55.0.13)(expo-server@55.0.8)(expo@55.0.17)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@expo/router-server@55.0.16(@expo/metro-runtime@55.0.10)(expo-constants@55.0.16)(expo-font@55.0.7)(expo-router@55.0.14)(expo-server@55.0.9)(expo@55.0.23)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: debug: 4.4.3 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.15(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) - expo-font: 55.0.6(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-server: 55.0.8 - react: 19.2.0 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) + expo-font: 55.0.7(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + expo-server: 55.0.9 + react: 19.2.6 optionalDependencies: - '@expo/metro-runtime': 55.0.10(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-dom@19.2.5(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-router: 55.0.13(121105c3b042d5e83ab3c1d3b84ed55f) - react-dom: 19.2.5(react@19.2.0) + '@expo/metro-runtime': 55.0.10(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-dom@19.2.5(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + expo-router: 55.0.14(021822249f1df9b3c5f483d3fca100f4) + react-dom: 19.2.5(react@19.2.6) transitivePeerDependencies: - supports-color - '@expo/schema-utils@55.0.3': {} + '@expo/schema-utils@55.0.4': {} '@expo/sdk-runtime-versions@1.0.0': {} @@ -7074,15 +6999,15 @@ snapshots: '@expo/sudo-prompt@9.3.2': {} - '@expo/vector-icons@15.1.1(expo-font@55.0.6)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@expo/vector-icons@15.1.1(expo-font@55.0.7)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: - expo-font: 55.0.6(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo-font: 55.0.7(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) '@expo/ws-tunnel@1.0.6': {} - '@expo/xcpretty@4.4.3': + '@expo/xcpretty@4.4.4': dependencies: '@babel/code-frame': 7.29.0 chalk: 4.1.2 @@ -7374,265 +7299,263 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.47.0': optional: true - '@oxlint/binding-android-arm-eabi@1.62.0': + '@oxlint/binding-android-arm-eabi@1.63.0': optional: true - '@oxlint/binding-android-arm64@1.62.0': + '@oxlint/binding-android-arm64@1.63.0': optional: true - '@oxlint/binding-darwin-arm64@1.62.0': + '@oxlint/binding-darwin-arm64@1.63.0': optional: true - '@oxlint/binding-darwin-x64@1.62.0': + '@oxlint/binding-darwin-x64@1.63.0': optional: true - '@oxlint/binding-freebsd-x64@1.62.0': + '@oxlint/binding-freebsd-x64@1.63.0': optional: true - '@oxlint/binding-linux-arm-gnueabihf@1.62.0': + '@oxlint/binding-linux-arm-gnueabihf@1.63.0': optional: true - '@oxlint/binding-linux-arm-musleabihf@1.62.0': + '@oxlint/binding-linux-arm-musleabihf@1.63.0': optional: true - '@oxlint/binding-linux-arm64-gnu@1.62.0': + '@oxlint/binding-linux-arm64-gnu@1.63.0': optional: true - '@oxlint/binding-linux-arm64-musl@1.62.0': + '@oxlint/binding-linux-arm64-musl@1.63.0': optional: true - '@oxlint/binding-linux-ppc64-gnu@1.62.0': + '@oxlint/binding-linux-ppc64-gnu@1.63.0': optional: true - '@oxlint/binding-linux-riscv64-gnu@1.62.0': + '@oxlint/binding-linux-riscv64-gnu@1.63.0': optional: true - '@oxlint/binding-linux-riscv64-musl@1.62.0': + '@oxlint/binding-linux-riscv64-musl@1.63.0': optional: true - '@oxlint/binding-linux-s390x-gnu@1.62.0': + '@oxlint/binding-linux-s390x-gnu@1.63.0': optional: true - '@oxlint/binding-linux-x64-gnu@1.62.0': + '@oxlint/binding-linux-x64-gnu@1.63.0': optional: true - '@oxlint/binding-linux-x64-musl@1.62.0': + '@oxlint/binding-linux-x64-musl@1.63.0': optional: true - '@oxlint/binding-openharmony-arm64@1.62.0': + '@oxlint/binding-openharmony-arm64@1.63.0': optional: true - '@oxlint/binding-win32-arm64-msvc@1.62.0': + '@oxlint/binding-win32-arm64-msvc@1.63.0': optional: true - '@oxlint/binding-win32-ia32-msvc@1.62.0': + '@oxlint/binding-win32-ia32-msvc@1.63.0': optional: true - '@oxlint/binding-win32-x64-msvc@1.62.0': + '@oxlint/binding-win32-x64-msvc@1.63.0': optional: true '@pkgr/core@0.2.9': {} '@radix-ui/primitive@1.1.3': {} - '@radix-ui/react-collection@1.1.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-collection@1.1.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.0 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-context@1.1.2(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.0 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-dialog@1.1.15(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-dialog@1.1.15(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-focus-scope': 1.1.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-portal': 1.1.9(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-focus-scope': 1.1.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-portal': 1.1.9(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) aria-hidden: 1.2.6 - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) - react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) + react-remove-scroll: 2.7.2(@types/react@19.2.14)(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-direction@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.0 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-dismissable-layer@1.1.11(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-focus-guards@1.1.3(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.0 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-focus-scope@1.1.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-focus-scope@1.1.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-id@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-portal@1.1.9(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-portal@1.1.9(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-presence@1.1.5(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-presence@1.1.5(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-primitive@2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-primitive@2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-slot': 1.2.3(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-roving-focus@1.1.11(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-roving-focus@1.1.11(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-collection': 1.1.7(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-slot@1.2.3(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-slot@1.2.4(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-tabs@1.1.13(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0)': + '@radix-ui/react-tabs@1.1.13(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6)': dependencies: '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-presence': 1.1.5(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-roving-focus': 1.1.11(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-context': 1.1.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-direction': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-id': 1.1.1(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-presence': 1.1.5(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-primitive': 2.1.3(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-roving-focus': 1.1.11(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.0 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.0) - react: 19.2.0 + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.14)(react@19.2.6) + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.0)': + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.2.14)(react@19.2.6)': dependencies: - react: 19.2.0 + react: 19.2.6 optionalDependencies: '@types/react': 19.2.14 - '@react-native-async-storage/async-storage@2.2.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))': + '@react-native-async-storage/async-storage@2.2.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))': dependencies: merge-options: 3.0.4 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - '@react-native/assets-registry@0.83.6': {} - - '@react-native/assets-registry@0.85.2': {} + '@react-native/assets-registry@0.83.9': {} '@react-native/babel-plugin-codegen@0.83.6(@babel/core@7.29.0)': dependencies: @@ -7649,7 +7572,6 @@ snapshots: transitivePeerDependencies: - '@babel/core' - supports-color - optional: true '@react-native/babel-preset@0.83.6(@babel/core@7.29.0)': dependencies: @@ -7738,12 +7660,21 @@ snapshots: react-refresh: 0.14.2 transitivePeerDependencies: - supports-color - optional: true '@react-native/codegen@0.83.6(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 + glob: 7.2.3 + hermes-parser: 0.32.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + yargs: 17.7.2 + + '@react-native/codegen@0.83.9(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.3 glob: 7.2.3 hermes-parser: 0.32.0 invariant: 2.2.4 @@ -7753,37 +7684,21 @@ snapshots: '@react-native/codegen@0.85.2(@babel/core@7.29.0)': dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 hermes-parser: 0.33.3 invariant: 2.2.4 nullthrows: 1.1.1 tinyglobby: 0.2.16 yargs: 17.7.2 - '@react-native/community-cli-plugin@0.83.6(@react-native/metro-config@0.85.2(@babel/core@7.29.0))': + '@react-native/community-cli-plugin@0.83.9(@react-native/metro-config@0.85.2(@babel/core@7.29.0))': dependencies: - '@react-native/dev-middleware': 0.83.6 + '@react-native/dev-middleware': 0.83.9 debug: 4.4.3 invariant: 2.2.4 - metro: 0.83.6 - metro-config: 0.83.6 - metro-core: 0.83.6 - semver: 7.7.4 - optionalDependencies: - '@react-native/metro-config': 0.85.2(@babel/core@7.29.0) - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate - - '@react-native/community-cli-plugin@0.85.2(@react-native/metro-config@0.85.2(@babel/core@7.29.0))': - dependencies: - '@react-native/dev-middleware': 0.85.2 - debug: 4.4.3 - invariant: 2.2.4 - metro: 0.84.3 - metro-config: 0.84.3 - metro-core: 0.84.3 + metro: 0.83.7 + metro-config: 0.83.7 + metro-core: 0.83.7 semver: 7.7.4 optionalDependencies: '@react-native/metro-config': 0.85.2(@babel/core@7.29.0) @@ -7794,20 +7709,17 @@ snapshots: '@react-native/debugger-frontend@0.83.6': {} - '@react-native/debugger-frontend@0.85.2': {} + '@react-native/debugger-frontend@0.83.9': {} '@react-native/debugger-shell@0.83.6': dependencies: cross-spawn: 7.0.6 fb-dotslash: 0.5.8 - '@react-native/debugger-shell@0.85.2': + '@react-native/debugger-shell@0.83.9': dependencies: cross-spawn: 7.0.6 - debug: 4.4.3 fb-dotslash: 0.5.8 - transitivePeerDependencies: - - supports-color '@react-native/dev-middleware@0.83.6': dependencies: @@ -7828,13 +7740,13 @@ snapshots: - supports-color - utf-8-validate - '@react-native/dev-middleware@0.85.2': + '@react-native/dev-middleware@0.83.9': dependencies: '@isaacs/ttlcache': 1.4.1 - '@react-native/debugger-frontend': 0.85.2 - '@react-native/debugger-shell': 0.85.2 + '@react-native/debugger-frontend': 0.83.9 + '@react-native/debugger-shell': 0.83.9 chrome-launcher: 0.15.2 - chromium-edge-launcher: 0.3.0 + chromium-edge-launcher: 0.2.0 connect: 3.7.0 debug: 4.4.3 invariant: 2.2.4 @@ -7847,11 +7759,9 @@ snapshots: - supports-color - utf-8-validate - '@react-native/gradle-plugin@0.83.6': {} + '@react-native/gradle-plugin@0.83.9': {} - '@react-native/gradle-plugin@0.85.2': {} - - '@react-native/js-polyfills@0.83.6': {} + '@react-native/js-polyfills@0.83.9': {} '@react-native/js-polyfills@0.85.2': {} @@ -7863,107 +7773,96 @@ snapshots: nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - optional: true '@react-native/metro-config@0.85.2(@babel/core@7.29.0)': dependencies: '@react-native/js-polyfills': 0.85.2 '@react-native/metro-babel-transformer': 0.85.2(@babel/core@7.29.0) - metro-config: 0.84.3 - metro-runtime: 0.84.3 + metro-config: 0.84.4 + metro-runtime: 0.84.4 transitivePeerDependencies: - '@babel/core' - bufferutil - supports-color - utf-8-validate - optional: true '@react-native/normalize-colors@0.74.89': {} '@react-native/normalize-colors@0.83.6': {} - '@react-native/normalize-colors@0.85.2': {} + '@react-native/normalize-colors@0.83.9': {} - '@react-native/virtualized-lists@0.83.6(@types/react@19.2.14)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@react-native/virtualized-lists@0.83.9(@types/react@19.2.14)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: invariant: 2.2.4 nullthrows: 1.1.1 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - '@react-native/virtualized-lists@0.85.2(@types/react@19.2.14)(react-native@0.85.2(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@react-navigation/bottom-tabs@7.15.11(@react-navigation/native@7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-safe-area-context@5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-screens@4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: - invariant: 2.2.4 - nullthrows: 1.1.1 - react: 19.2.0 - react-native: 0.85.2(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - optionalDependencies: - '@types/react': 19.2.14 - - '@react-navigation/bottom-tabs@7.15.10(@react-navigation/native@7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-screens@4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': - dependencies: - '@react-navigation/elements': 2.9.15(@react-navigation/native@7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@react-navigation/native': 7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@react-navigation/elements': 2.9.15(@react-navigation/native@7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-safe-area-context@5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@react-navigation/native': 7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) color: 4.2.3 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-native-safe-area-context: 5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react-native-screens: 4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-native-safe-area-context: 5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react-native-screens: 4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) sf-symbols-typescript: 2.2.0 transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/core@7.17.2(react@19.2.0)': + '@react-navigation/core@7.17.2(react@19.2.6)': dependencies: '@react-navigation/routers': 7.5.3 escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 - nanoid: 3.3.11 + nanoid: 3.3.12 query-string: 7.1.3 - react: 19.2.0 - react-is: 19.2.5 - use-latest-callback: 0.2.6(react@19.2.0) - use-sync-external-store: 1.6.0(react@19.2.0) + react: 19.2.6 + react-is: 19.2.6 + use-latest-callback: 0.2.6(react@19.2.6) + use-sync-external-store: 1.6.0(react@19.2.6) - '@react-navigation/elements@2.9.15(@react-navigation/native@7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@react-navigation/elements@2.9.15(@react-navigation/native@7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-safe-area-context@5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: - '@react-navigation/native': 7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@react-navigation/native': 7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) color: 4.2.3 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-native-safe-area-context: 5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - use-latest-callback: 0.2.6(react@19.2.0) - use-sync-external-store: 1.6.0(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-native-safe-area-context: 5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + use-latest-callback: 0.2.6(react@19.2.6) + use-sync-external-store: 1.6.0(react@19.2.6) - '@react-navigation/native-stack@7.14.12(@react-navigation/native@7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-screens@4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@react-navigation/native-stack@7.14.12(@react-navigation/native@7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-safe-area-context@5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-screens@4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: - '@react-navigation/elements': 2.9.15(@react-navigation/native@7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@react-navigation/native': 7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@react-navigation/elements': 2.9.15(@react-navigation/native@7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-safe-area-context@5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@react-navigation/native': 7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) color: 4.2.3 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-native-safe-area-context: 5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react-native-screens: 4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-native-safe-area-context: 5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react-native-screens: 4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) sf-symbols-typescript: 2.2.0 warn-once: 0.1.1 transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@react-navigation/native@7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)': + '@react-navigation/native@7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)': dependencies: - '@react-navigation/core': 7.17.2(react@19.2.0) + '@react-navigation/core': 7.17.2(react@19.2.6) escape-string-regexp: 4.0.0 fast-deep-equal: 3.1.3 - nanoid: 3.3.11 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - use-latest-callback: 0.2.6(react@19.2.0) + nanoid: 3.3.12 + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + use-latest-callback: 0.2.6(react@19.2.6) '@react-navigation/routers@7.5.3': dependencies: - nanoid: 3.3.11 + nanoid: 3.3.12 '@rtsao/scc@1.1.0': {} @@ -7979,25 +7878,25 @@ snapshots: dependencies: '@sinonjs/commons': 3.0.1 - '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react-test-renderer@19.2.0(react@19.2.0))(react@19.2.0)': + '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react-test-renderer@19.2.0(react@19.2.6))(react@19.2.6)': dependencies: jest-matcher-utils: 30.3.0 picocolors: 1.1.1 pretty-format: 30.3.0 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-test-renderer: 19.2.0(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-test-renderer: 19.2.0(react@19.2.6) redent: 3.0.0 optionalDependencies: jest: 29.7.0(@types/node@25.6.0) - '@tootallnate/once@2.0.0': {} + '@tootallnate/once@2.0.1': {} '@tsconfig/node18@18.2.6': {} '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 @@ -8009,7 +7908,7 @@ snapshots: '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': @@ -8018,7 +7917,7 @@ snapshots: '@types/emscripten@1.41.5': {} - '@types/estree@1.0.8': {} + '@types/estree@1.0.9': {} '@types/graceful-fs@4.1.9': dependencies: @@ -8055,13 +7954,12 @@ snapshots: dependencies: undici-types: 7.19.2 - '@types/react-native@0.73.0(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)': + '@types/react-native@0.73.0(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)': dependencies: - react-native: 0.85.2(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) transitivePeerDependencies: - '@babel/core' - '@react-native-community/cli' - - '@react-native/jest-preset' - '@react-native/metro-config' - '@types/react' - bufferutil @@ -8069,6 +7967,10 @@ snapshots: - supports-color - utf-8-validate + '@types/react-test-renderer@19.1.0': + dependencies: + '@types/react': 19.2.14 + '@types/react@19.2.14': dependencies: csstype: 3.2.3 @@ -8087,14 +7989,14 @@ snapshots: dependencies: '@types/yargs-parser': 21.0.3 - '@typescript-eslint/eslint-plugin@8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/type-utils': 8.59.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.1(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.1 + '@typescript-eslint/parser': 8.59.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/type-utils': 8.59.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.2(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.2 eslint: 9.39.4 ignore: 7.0.5 natural-compare: 1.4.0 @@ -8103,41 +8005,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.1 + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.59.2 debug: 4.4.3 eslint: 9.39.4 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.1(typescript@5.9.3)': + '@typescript-eslint/project-service@8.59.2(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.9.3) - '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@5.9.3) + '@typescript-eslint/types': 8.59.2 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.1': + '@typescript-eslint/scope-manager@8.59.2': dependencies: - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/visitor-keys': 8.59.1 + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/visitor-keys': 8.59.2 - '@typescript-eslint/tsconfig-utils@8.59.1(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.59.2(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.59.1(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.59.2(eslint@9.39.4)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.1(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) + '@typescript-eslint/utils': 8.59.2(eslint@9.39.4)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.4 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -8145,14 +8047,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.1': {} + '@typescript-eslint/types@8.59.2': {} - '@typescript-eslint/typescript-estree@8.59.1(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.59.2(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.59.1(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.59.1(typescript@5.9.3) - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/visitor-keys': 8.59.1 + '@typescript-eslint/project-service': 8.59.2(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.59.2(typescript@5.9.3) + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/visitor-keys': 8.59.2 debug: 4.4.3 minimatch: 10.2.5 semver: 7.7.4 @@ -8162,23 +8064,23 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.1(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/utils@8.59.2(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@typescript-eslint/scope-manager': 8.59.1 - '@typescript-eslint/types': 8.59.1 - '@typescript-eslint/typescript-estree': 8.59.1(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.59.2 + '@typescript-eslint/types': 8.59.2 + '@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3) eslint: 9.39.4 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.1': + '@typescript-eslint/visitor-keys@8.59.2': dependencies: - '@typescript-eslint/types': 8.59.1 + '@typescript-eslint/types': 8.59.2 eslint-visitor-keys: 5.0.1 - '@ungap/structured-clone@1.3.0': {} + '@ungap/structured-clone@1.3.1': {} '@xmldom/xmldom@0.8.13': {} @@ -8384,7 +8286,7 @@ snapshots: babel-plugin-polyfill-corejs2@0.4.17(@babel/core@7.29.0): dependencies: - '@babel/compat-data': 7.29.0 + '@babel/compat-data': 7.29.3 '@babel/core': 7.29.0 '@babel/helper-define-polyfill-provider': 0.6.8(@babel/core@7.29.0) semver: 6.3.1 @@ -8457,7 +8359,7 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) - babel-preset-expo@55.0.18(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.17)(react-refresh@0.14.2): + babel-preset-expo@55.0.21(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.23)(react-refresh@0.14.2): dependencies: '@babel/generator': 7.29.1 '@babel/helper-module-imports': 7.28.6 @@ -8485,7 +8387,7 @@ snapshots: resolve-from: 5.0.0 optionalDependencies: '@babel/runtime': 7.29.2 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) transitivePeerDependencies: - '@babel/core' - supports-color @@ -8502,15 +8404,15 @@ snapshots: balanced-match@4.0.4: {} - barcode-detector@3.1.2(@types/emscripten@1.41.5): + barcode-detector@3.1.3(@types/emscripten@1.41.5): dependencies: - zxing-wasm: 3.0.2(@types/emscripten@1.41.5) + zxing-wasm: 3.0.3(@types/emscripten@1.41.5) transitivePeerDependencies: - '@types/emscripten' base64-js@1.5.1: {} - baseline-browser-mapping@2.10.23: {} + baseline-browser-mapping@2.10.27: {} better-opn@3.0.2: dependencies: @@ -8550,9 +8452,9 @@ snapshots: browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.10.23 - caniuse-lite: 1.0.30001791 - electron-to-chromium: 1.5.344 + baseline-browser-mapping: 2.10.27 + caniuse-lite: 1.0.30001792 + electron-to-chromium: 1.5.352 node-releases: 2.0.38 update-browserslist-db: 1.2.3(browserslist@4.28.2) @@ -8591,7 +8493,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001791: {} + caniuse-lite@1.0.30001792: {} chalk@2.4.2: dependencies: @@ -8646,16 +8548,6 @@ snapshots: transitivePeerDependencies: - supports-color - chromium-edge-launcher@0.3.0: - dependencies: - '@types/node': 25.6.0 - escape-string-regexp: 4.0.0 - is-wsl: 2.2.0 - lighthouse-logger: 1.4.2 - mkdirp: 1.0.4 - transitivePeerDependencies: - - supports-color - ci-info@2.0.0: {} ci-info@3.9.0: {} @@ -8919,7 +8811,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.344: {} + electron-to-chromium@1.5.352: {} emittery@0.13.1: {} @@ -9103,13 +8995,13 @@ snapshots: dependencies: eslint: 9.39.4 - eslint-config-universe@15.0.3(eslint@9.39.4)(prettier@2.8.8)(typescript@5.9.3): + eslint-config-universe@15.0.4(eslint@9.39.4)(prettier@2.8.8)(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.59.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/parser': 8.59.1(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.2(eslint@9.39.4)(typescript@5.9.3) eslint: 9.39.4 eslint-config-prettier: 9.1.2(eslint@9.39.4) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4) eslint-plugin-n: 17.24.0(eslint@9.39.4)(typescript@5.9.3) eslint-plugin-node: 11.1.0(eslint@9.39.4) eslint-plugin-prettier: 5.5.5(eslint-config-prettier@9.1.2(eslint@9.39.4))(eslint@9.39.4)(prettier@2.8.8) @@ -9128,16 +9020,16 @@ snapshots: eslint-import-resolver-node@0.3.10: dependencies: debug: 3.2.7 - is-core-module: 2.16.1 + is-core-module: 2.16.2 resolve: 2.0.0-next.6 transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.59.1(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.2(eslint@9.39.4)(typescript@5.9.3) eslint: 9.39.4 eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: @@ -9156,7 +9048,7 @@ snapshots: eslint-utils: 2.1.0 regexpp: 3.2.0 - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9167,9 +9059,9 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.4 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.1(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4) hasown: 2.0.3 - is-core-module: 2.16.1 + is-core-module: 2.16.2 is-glob: 4.0.3 minimatch: 3.1.5 object.fromentries: 2.0.8 @@ -9179,7 +9071,7 @@ snapshots: string.prototype.trimend: 1.0.9 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.59.1(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/parser': 8.59.2(eslint@9.39.4)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -9275,7 +9167,7 @@ snapshots: '@humanfs/node': 0.16.8 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 - '@types/estree': 1.0.8 + '@types/estree': 1.0.9 ajv: 6.15.0 chalk: 4.1.2 cross-spawn: 7.0.6 @@ -9347,114 +9239,120 @@ snapshots: jest-message-util: 29.7.0 jest-util: 29.7.0 - expo-application@55.0.14(expo@55.0.17): + expo-application@55.0.14(expo@55.0.23): dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) - expo-asset@55.0.16(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + expo-asset@55.0.17(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3): dependencies: - '@expo/image-utils': 0.8.13(typescript@5.9.3) - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.15(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + '@expo/image-utils': 0.8.14(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) transitivePeerDependencies: - supports-color - typescript - expo-build-properties@55.0.13(expo@55.0.17): + expo-build-properties@55.0.13(expo@55.0.23): dependencies: - '@expo/schema-utils': 55.0.3 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + '@expo/schema-utils': 55.0.4 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) resolve-from: 5.0.0 semver: 7.7.4 - expo-camera@55.0.16(@types/emscripten@1.41.5)(expo@55.0.17)(react-native-web@0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-camera@55.0.18(@types/emscripten@1.41.5)(expo@55.0.23)(react-native-web@0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - barcode-detector: 3.1.2(@types/emscripten@1.41.5) - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + barcode-detector: 3.1.3(@types/emscripten@1.41.5) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) optionalDependencies: - react-native-web: 0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0) + react-native-web: 0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6) transitivePeerDependencies: - '@types/emscripten' - expo-constants@55.0.15(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)): + expo-clipboard@55.0.13(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - '@expo/env': 2.1.1 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + + expo-constants@55.0.16(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)): + dependencies: + '@expo/env': 2.1.2 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) transitivePeerDependencies: - supports-color - expo-crypto@55.0.14(expo@55.0.17): + expo-crypto@55.0.14(expo@55.0.23): dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) - expo-file-system@55.0.17(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)): + expo-file-system@55.0.19(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)): dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - expo-font@55.0.6(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-font@55.0.7(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) fontfaceobserver: 2.3.0 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - expo-glass-effect@55.0.10(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-glass-effect@55.0.11(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - expo-haptics@55.0.14(expo@55.0.17): + expo-haptics@55.0.14(expo@55.0.23): dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) - expo-image@55.0.9(expo@55.0.17)(react-native-web@0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-image@55.0.10(expo@55.0.23)(react-native-web@0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) sf-symbols-typescript: 2.2.0 optionalDependencies: - react-native-web: 0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0) + react-native-web: 0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6) - expo-keep-awake@55.0.6(expo@55.0.17)(react@19.2.0): + expo-keep-awake@55.0.8(expo@55.0.23)(react@19.2.6): dependencies: - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - react: 19.2.0 + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + react: 19.2.6 - expo-linking@55.0.14(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-linking@55.0.15(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - expo-constants: 55.0.15(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) invariant: 2.2.4 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) transitivePeerDependencies: - expo - supports-color - expo-module-scripts@55.0.2(@babel/core@7.29.0)(@babel/runtime@7.29.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(eslint@9.39.4)(expo@55.0.17)(jest@29.7.0(@types/node@25.6.0))(prettier@2.8.8)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react-refresh@0.14.2)(react-test-renderer@19.2.0(react@19.2.0))(react@19.2.0): + expo-module-scripts@55.0.2(@babel/core@7.29.0)(@babel/runtime@7.29.2)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(eslint@9.39.4)(expo@55.0.23)(jest@29.7.0(@types/node@25.6.0))(prettier@2.8.8)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react-refresh@0.14.2)(react-test-renderer@19.2.0(react@19.2.6))(react@19.2.6): dependencies: '@babel/cli': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) - '@babel/preset-env': 7.29.2(@babel/core@7.29.0) + '@babel/preset-env': 7.29.5(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@expo/npm-proofread': 1.0.1 '@expo/spawn-async': 1.7.2 - '@testing-library/react-native': 13.3.3(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react-test-renderer@19.2.0(react@19.2.0))(react@19.2.0) + '@testing-library/react-native': 13.3.3(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react-test-renderer@19.2.0(react@19.2.6))(react@19.2.6) '@tsconfig/node18': 18.2.6 '@types/jest': 29.5.14 babel-plugin-dynamic-import-node: 2.3.3 - babel-preset-expo: 55.0.18(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.17)(react-refresh@0.14.2) + babel-preset-expo: 55.0.21(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.23)(react-refresh@0.14.2) commander: 12.1.0 - eslint-config-universe: 15.0.3(eslint@9.39.4)(prettier@2.8.8)(typescript@5.9.3) + eslint-config-universe: 15.0.4(eslint@9.39.4)(prettier@2.8.8)(typescript@5.9.3) glob: 13.0.6 - jest-expo: 55.0.16(@babel/core@7.29.0)(expo@55.0.17)(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + jest-expo: 55.0.17(@babel/core@7.29.0)(expo@55.0.23)(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) jest-snapshot-prettier: prettier@2.8.8 jest-watch-typeahead: 2.2.1(jest@29.7.0(@types/node@25.6.0)) resolve-workspace-root: 2.0.1 @@ -9484,9 +9382,9 @@ snapshots: - supports-color - utf-8-validate - expo-modules-autolinking@55.0.18(typescript@5.9.3): + expo-modules-autolinking@55.0.21(typescript@5.9.3): dependencies: - '@expo/require-utils': 55.0.4(typescript@5.9.3) + '@expo/require-utils': 55.0.5(typescript@5.9.3) '@expo/spawn-async': 1.7.2 chalk: 4.1.2 commander: 7.2.0 @@ -9494,70 +9392,70 @@ snapshots: - supports-color - typescript - expo-modules-core@55.0.23(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-modules-core@55.0.25(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: invariant: 2.2.4 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) optionalDependencies: - react-native-worklets: 0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + react-native-worklets: 0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) - expo-notifications@55.0.21(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + expo-notifications@55.0.22(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3): dependencies: - '@expo/image-utils': 0.8.13(typescript@5.9.3) + '@expo/image-utils': 0.8.14(typescript@5.9.3) abort-controller: 3.0.0 badgin: 1.2.3 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-application: 55.0.14(expo@55.0.17) - expo-constants: 55.0.15(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + expo-application: 55.0.14(expo@55.0.23) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) transitivePeerDependencies: - supports-color - typescript - expo-router@55.0.13(121105c3b042d5e83ab3c1d3b84ed55f): + expo-router@55.0.14(021822249f1df9b3c5f483d3fca100f4): dependencies: - '@expo/log-box': 55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@expo/metro-runtime': 55.0.10(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-dom@19.2.5(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@expo/schema-utils': 55.0.3 - '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.0) - '@radix-ui/react-tabs': 1.1.13(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - '@react-navigation/bottom-tabs': 7.15.10(@react-navigation/native@7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-screens@4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@react-navigation/native': 7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@react-navigation/native-stack': 7.14.12(@react-navigation/native@7.2.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-safe-area-context@5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-screens@4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/log-box': 55.0.12(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@expo/metro-runtime': 55.0.10(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-dom@19.2.5(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@expo/schema-utils': 55.0.4 + '@radix-ui/react-slot': 1.2.4(@types/react@19.2.14)(react@19.2.6) + '@radix-ui/react-tabs': 1.1.13(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + '@react-navigation/bottom-tabs': 7.15.11(@react-navigation/native@7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-safe-area-context@5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-screens@4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@react-navigation/native': 7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@react-navigation/native-stack': 7.14.12(@react-navigation/native@7.2.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-safe-area-context@5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-screens@4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) client-only: 0.0.1 debug: 4.4.3 escape-string-regexp: 4.0.0 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.15(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) - expo-glass-effect: 55.0.10(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-image: 55.0.9(expo@55.0.17)(react-native-web@0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-linking: 55.0.14(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-server: 55.0.8 - expo-symbols: 55.0.7(expo-font@55.0.6)(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) + expo-glass-effect: 55.0.11(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + expo-image: 55.0.10(expo@55.0.23)(react-native-web@0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + expo-linking: 55.0.15(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + expo-server: 55.0.9 + expo-symbols: 55.0.8(expo-font@55.0.7)(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) fast-deep-equal: 3.1.3 invariant: 2.2.4 - nanoid: 3.3.11 + nanoid: 3.3.12 query-string: 7.1.3 - react: 19.2.0 + react: 19.2.6 react-fast-compare: 3.2.2 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-native-is-edge-to-edge: 1.3.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react-native-safe-area-context: 5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react-native-screens: 4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-native-is-edge-to-edge: 1.3.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react-native-safe-area-context: 5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react-native-screens: 4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) semver: 7.6.3 server-only: 0.0.1 sf-symbols-typescript: 2.2.0 shallowequal: 1.1.0 - use-latest-callback: 0.2.6(react@19.2.0) - vaul: 1.1.2(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) + use-latest-callback: 0.2.6(react@19.2.6) + vaul: 1.1.2(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) optionalDependencies: - '@testing-library/react-native': 13.3.3(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react-test-renderer@19.2.0(react@19.2.0))(react@19.2.0) - react-dom: 19.2.5(react@19.2.0) - react-native-gesture-handler: 2.30.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react-native-reanimated: 4.2.1(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react-native-web: 0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0) + '@testing-library/react-native': 13.3.3(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react-test-renderer@19.2.0(react@19.2.6))(react@19.2.6) + react-dom: 19.2.5(react@19.2.6) + react-native-gesture-handler: 2.31.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react-native-reanimated: 4.3.0(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react-native-web: 0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6) transitivePeerDependencies: - '@react-native-masked-view/masked-view' - '@types/react' @@ -9565,62 +9463,66 @@ snapshots: - expo-font - supports-color - expo-server@55.0.8: {} - - expo-splash-screen@55.0.19(expo@55.0.17)(typescript@5.9.3): + expo-secure-store@55.0.13(expo@55.0.23): dependencies: - '@expo/prebuild-config': 55.0.16(expo@55.0.17)(typescript@5.9.3) - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + + expo-server@55.0.9: {} + + expo-splash-screen@55.0.20(expo@55.0.23)(typescript@5.9.3): + dependencies: + '@expo/prebuild-config': 55.0.17(expo@55.0.23)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript - expo-status-bar@55.0.5(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-status-bar@55.0.6(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-native-is-edge-to-edge: 1.3.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-native-is-edge-to-edge: 1.3.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) - expo-symbols@55.0.7(expo-font@55.0.6)(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + expo-symbols@55.0.8(expo-font@55.0.7)(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: '@expo-google-fonts/material-symbols': 0.4.34 - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-font: 55.0.6(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + expo-font: 55.0.7(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) sf-symbols-typescript: 2.2.0 - expo@55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + expo@55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 - '@expo/cli': 55.0.26(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-constants@55.0.15)(expo-font@55.0.6)(expo-router@55.0.13)(expo@55.0.17)(react-dom@19.2.5(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - '@expo/config': 55.0.15(typescript@5.9.3) + '@expo/cli': 55.0.29(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-constants@55.0.16)(expo-font@55.0.7)(expo-router@55.0.14)(expo@55.0.23)(react-dom@19.2.5(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + '@expo/config': 55.0.16(typescript@5.9.3) '@expo/config-plugins': 55.0.8 - '@expo/devtools': 55.0.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@expo/fingerprint': 0.16.6 - '@expo/local-build-cache-provider': 55.0.11(typescript@5.9.3) - '@expo/log-box': 55.0.11(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@expo/metro': 55.1.0 - '@expo/metro-config': 55.0.17(expo@55.0.17)(typescript@5.9.3) - '@expo/vector-icons': 15.1.1(expo-font@55.0.6)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@ungap/structured-clone': 1.3.0 - babel-preset-expo: 55.0.18(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.17)(react-refresh@0.14.2) - expo-asset: 55.0.16(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) - expo-constants: 55.0.15(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) - expo-file-system: 55.0.17(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0)) - expo-font: 55.0.6(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - expo-keep-awake: 55.0.6(expo@55.0.17)(react@19.2.0) - expo-modules-autolinking: 55.0.18(typescript@5.9.3) - expo-modules-core: 55.0.23(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/devtools': 55.0.3(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@expo/fingerprint': 0.16.7 + '@expo/local-build-cache-provider': 55.0.12(typescript@5.9.3) + '@expo/log-box': 55.0.12(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@expo/metro': 55.1.1 + '@expo/metro-config': 55.0.20(expo@55.0.23)(typescript@5.9.3) + '@expo/vector-icons': 15.1.1(expo-font@55.0.7)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@ungap/structured-clone': 1.3.1 + babel-preset-expo: 55.0.21(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@55.0.23)(react-refresh@0.14.2) + expo-asset: 55.0.17(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) + expo-constants: 55.0.16(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) + expo-file-system: 55.0.19(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6)) + expo-font: 55.0.7(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + expo-keep-awake: 55.0.8(expo@55.0.23)(react@19.2.6) + expo-modules-autolinking: 55.0.21(typescript@5.9.3) + expo-modules-core: 55.0.25(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) pretty-format: 29.7.0 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) react-refresh: 0.14.2 whatwg-url-minimum: 0.1.1 optionalDependencies: - '@expo/dom-webview': 55.0.5(expo@55.0.17)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - '@expo/metro-runtime': 55.0.10(@expo/dom-webview@55.0.5)(expo@55.0.17)(react-dom@19.2.5(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react-native-webview: 13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@expo/dom-webview': 55.0.5(expo@55.0.23)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + '@expo/metro-runtime': 55.0.10(@expo/dom-webview@55.0.5)(expo@55.0.23)(react-dom@19.2.5(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react-native-webview: 13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) transitivePeerDependencies: - '@babel/core' - bufferutil @@ -9855,8 +9757,6 @@ snapshots: hermes-compiler@0.14.1: {} - hermes-compiler@250829098.0.10: {} - hermes-estree@0.32.0: {} hermes-estree@0.32.1: {} @@ -9905,7 +9805,7 @@ snapshots: http-proxy-agent@5.0.0: dependencies: - '@tootallnate/once': 2.0.0 + '@tootallnate/once': 2.0.1 agent-base: 6.0.2 debug: 4.4.3 transitivePeerDependencies: @@ -10010,7 +9910,7 @@ snapshots: is-callable@1.2.7: {} - is-core-module@2.16.1: + is-core-module@2.16.2: dependencies: hasown: 2.0.3 @@ -10118,7 +10018,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -10128,7 +10028,7 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: '@babel/core': 7.29.0 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 semver: 7.7.4 @@ -10294,22 +10194,22 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 - jest-expo@55.0.16(@babel/core@7.29.0)(expo@55.0.17)(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3): + jest-expo@55.0.17(@babel/core@7.29.0)(expo@55.0.23)(jest@29.7.0(@types/node@25.6.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3): dependencies: - '@expo/config': 55.0.15(typescript@5.9.3) - '@expo/json-file': 10.0.13 + '@expo/config': 55.0.16(typescript@5.9.3) + '@expo/json-file': 10.0.14 '@jest/create-cache-key-function': 29.7.0 '@jest/globals': 29.7.0 babel-jest: 29.7.0(@babel/core@7.29.0) - expo: 55.0.17(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.13)(react-dom@19.2.5(react@19.2.0))(react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0)(typescript@5.9.3) + expo: 55.0.23(@babel/core@7.29.0)(@expo/dom-webview@55.0.5)(@expo/metro-runtime@55.0.10)(expo-router@55.0.14)(react-dom@19.2.5(react@19.2.6))(react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6)(typescript@5.9.3) jest-environment-jsdom: 29.7.0 jest-snapshot: 29.7.0 jest-watch-select-projects: 2.0.0 jest-watch-typeahead: 2.2.1(jest@29.7.0(@types/node@25.6.0)) json5: 2.2.3 lodash: 4.18.1 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-test-renderer: 19.2.0(react@19.2.0) + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-test-renderer: 19.2.0(react@19.2.6) server-only: 0.0.1 stacktrace-js: 2.0.2 transitivePeerDependencies: @@ -10717,17 +10617,17 @@ snapshots: lru-cache@10.4.3: {} - lru-cache@11.3.5: {} + lru-cache@11.3.6: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lucide-react-native@1.11.0(react-native-svg@15.15.4(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + lucide-react-native@1.14.0(react-native-svg@15.15.4(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-native-svg: 15.15.4(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-native-svg: 15.15.4(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) make-dir@2.1.0: dependencies: @@ -10760,95 +10660,95 @@ snapshots: merge-stream@2.0.0: {} - metro-babel-transformer@0.83.6: + metro-babel-transformer@0.83.7: dependencies: '@babel/core': 7.29.0 flow-enums-runtime: 0.0.6 hermes-parser: 0.35.0 - metro-cache-key: 0.83.6 + metro-cache-key: 0.83.7 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - metro-babel-transformer@0.84.3: + metro-babel-transformer@0.84.4: dependencies: '@babel/core': 7.29.0 flow-enums-runtime: 0.0.6 hermes-parser: 0.35.0 - metro-cache-key: 0.84.3 + metro-cache-key: 0.84.4 nullthrows: 1.1.1 transitivePeerDependencies: - supports-color - metro-cache-key@0.83.6: + metro-cache-key@0.83.7: dependencies: flow-enums-runtime: 0.0.6 - metro-cache-key@0.84.3: + metro-cache-key@0.84.4: dependencies: flow-enums-runtime: 0.0.6 - metro-cache@0.83.6: + metro-cache@0.83.7: dependencies: exponential-backoff: 3.1.3 flow-enums-runtime: 0.0.6 https-proxy-agent: 7.0.6 - metro-core: 0.83.6 + metro-core: 0.83.7 transitivePeerDependencies: - supports-color - metro-cache@0.84.3: + 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.3 + metro-core: 0.84.4 transitivePeerDependencies: - supports-color - metro-config@0.83.6: + metro-config@0.83.7: dependencies: connect: 3.7.0 flow-enums-runtime: 0.0.6 jest-validate: 29.7.0 - metro: 0.83.6 - metro-cache: 0.83.6 - metro-core: 0.83.6 - metro-runtime: 0.83.6 - yaml: 2.8.3 + metro: 0.83.7 + metro-cache: 0.83.7 + metro-core: 0.83.7 + metro-runtime: 0.83.7 + yaml: 2.8.4 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro-config@0.84.3: + metro-config@0.84.4: dependencies: connect: 3.7.0 flow-enums-runtime: 0.0.6 jest-validate: 29.7.0 - metro: 0.84.3 - metro-cache: 0.84.3 - metro-core: 0.84.3 - metro-runtime: 0.84.3 - yaml: 2.8.3 + metro: 0.84.4 + metro-cache: 0.84.4 + metro-core: 0.84.4 + metro-runtime: 0.84.4 + yaml: 2.8.4 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro-core@0.83.6: + metro-core@0.83.7: dependencies: flow-enums-runtime: 0.0.6 lodash.throttle: 4.1.1 - metro-resolver: 0.83.6 + metro-resolver: 0.83.7 - metro-core@0.84.3: + metro-core@0.84.4: dependencies: flow-enums-runtime: 0.0.6 lodash.throttle: 4.1.1 - metro-resolver: 0.84.3 + metro-resolver: 0.84.4 - metro-file-map@0.83.6: + metro-file-map@0.83.7: dependencies: debug: 4.4.3 fb-watchman: 2.0.2 @@ -10862,7 +10762,7 @@ snapshots: transitivePeerDependencies: - supports-color - metro-file-map@0.84.3: + metro-file-map@0.84.4: dependencies: debug: 4.4.3 fb-watchman: 2.0.2 @@ -10876,85 +10776,85 @@ snapshots: transitivePeerDependencies: - supports-color - metro-minify-terser@0.83.6: + metro-minify-terser@0.83.7: dependencies: flow-enums-runtime: 0.0.6 terser: 5.46.2 - metro-minify-terser@0.84.3: + metro-minify-terser@0.84.4: dependencies: flow-enums-runtime: 0.0.6 terser: 5.46.2 - metro-resolver@0.83.6: + metro-resolver@0.83.7: dependencies: flow-enums-runtime: 0.0.6 - metro-resolver@0.84.3: + metro-resolver@0.84.4: dependencies: flow-enums-runtime: 0.0.6 - metro-runtime@0.83.6: + metro-runtime@0.83.7: dependencies: '@babel/runtime': 7.29.2 flow-enums-runtime: 0.0.6 - metro-runtime@0.84.3: + metro-runtime@0.84.4: dependencies: '@babel/runtime': 7.29.2 flow-enums-runtime: 0.0.6 - metro-source-map@0.83.6: + metro-source-map@0.83.7: dependencies: '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-symbolicate: 0.83.6 + metro-symbolicate: 0.83.7 nullthrows: 1.1.1 - ob1: 0.83.6 + ob1: 0.83.7 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-source-map@0.84.3: + metro-source-map@0.84.4: dependencies: '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-symbolicate: 0.84.3 + metro-symbolicate: 0.84.4 nullthrows: 1.1.1 - ob1: 0.84.3 + ob1: 0.84.4 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-symbolicate@0.83.6: + metro-symbolicate@0.83.7: dependencies: flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-source-map: 0.83.6 + metro-source-map: 0.83.7 nullthrows: 1.1.1 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-symbolicate@0.84.3: + metro-symbolicate@0.84.4: dependencies: flow-enums-runtime: 0.0.6 invariant: 2.2.4 - metro-source-map: 0.84.3 + metro-source-map: 0.84.4 nullthrows: 1.1.1 source-map: 0.5.7 vlq: 1.0.1 transitivePeerDependencies: - supports-color - metro-transform-plugins@0.83.6: + metro-transform-plugins@0.83.7: dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -10965,7 +10865,7 @@ snapshots: transitivePeerDependencies: - supports-color - metro-transform-plugins@0.84.3: + metro-transform-plugins@0.84.4: dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -10976,57 +10876,56 @@ snapshots: transitivePeerDependencies: - supports-color - metro-transform-worker@0.83.6: + metro-transform-worker@0.83.7: dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 - metro: 0.83.6 - metro-babel-transformer: 0.83.6 - metro-cache: 0.83.6 - metro-cache-key: 0.83.6 - metro-minify-terser: 0.83.6 - metro-source-map: 0.83.6 - metro-transform-plugins: 0.83.6 + metro: 0.83.7 + metro-babel-transformer: 0.83.7 + metro-cache: 0.83.7 + metro-cache-key: 0.83.7 + metro-minify-terser: 0.83.7 + metro-source-map: 0.83.7 + metro-transform-plugins: 0.83.7 nullthrows: 1.1.1 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - metro-transform-worker@0.84.3: + metro-transform-worker@0.84.4: dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/types': 7.29.0 flow-enums-runtime: 0.0.6 - metro: 0.84.3 - metro-babel-transformer: 0.84.3 - metro-cache: 0.84.3 - metro-cache-key: 0.84.3 - metro-minify-terser: 0.84.3 - metro-source-map: 0.84.3 - metro-transform-plugins: 0.84.3 + 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 - metro@0.83.6: + metro@0.83.7: dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 accepts: 2.0.0 - chalk: 4.1.2 ci-info: 2.0.0 connect: 3.7.0 debug: 4.4.3 @@ -11039,18 +10938,18 @@ snapshots: jest-worker: 29.7.0 jsc-safe-url: 0.2.4 lodash.throttle: 4.1.1 - metro-babel-transformer: 0.83.6 - metro-cache: 0.83.6 - metro-cache-key: 0.83.6 - metro-config: 0.83.6 - metro-core: 0.83.6 - metro-file-map: 0.83.6 - metro-resolver: 0.83.6 - metro-runtime: 0.83.6 - metro-source-map: 0.83.6 - metro-symbolicate: 0.83.6 - metro-transform-plugins: 0.83.6 - metro-transform-worker: 0.83.6 + metro-babel-transformer: 0.83.7 + metro-cache: 0.83.7 + metro-cache-key: 0.83.7 + metro-config: 0.83.7 + metro-core: 0.83.7 + metro-file-map: 0.83.7 + metro-resolver: 0.83.7 + metro-runtime: 0.83.7 + metro-source-map: 0.83.7 + metro-symbolicate: 0.83.7 + metro-transform-plugins: 0.83.7 + metro-transform-worker: 0.83.7 mime-types: 3.0.2 nullthrows: 1.1.1 serialize-error: 2.1.0 @@ -11063,17 +10962,16 @@ snapshots: - supports-color - utf-8-validate - metro@0.84.3: + metro@0.84.4: dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 '@babel/generator': 7.29.1 - '@babel/parser': 7.29.2 + '@babel/parser': 7.29.3 '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 accepts: 2.0.0 - chalk: 4.1.2 ci-info: 2.0.0 connect: 3.7.0 debug: 4.4.3 @@ -11086,18 +10984,18 @@ snapshots: jest-worker: 29.7.0 jsc-safe-url: 0.2.4 lodash.throttle: 4.1.1 - metro-babel-transformer: 0.84.3 - metro-cache: 0.84.3 - metro-cache-key: 0.84.3 - metro-config: 0.84.3 - metro-core: 0.84.3 - metro-file-map: 0.84.3 - metro-resolver: 0.84.3 - metro-runtime: 0.84.3 - metro-source-map: 0.84.3 - metro-symbolicate: 0.84.3 - metro-transform-plugins: 0.84.3 - metro-transform-worker: 0.84.3 + 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 @@ -11155,7 +11053,7 @@ snapshots: multitars@1.0.0: {} - nanoid@3.3.11: {} + nanoid@3.3.12: {} natural-compare@1.4.0: {} @@ -11203,11 +11101,11 @@ snapshots: nwsapi@2.2.23: {} - ob1@0.83.6: + ob1@0.83.7: dependencies: flow-enums-runtime: 0.0.6 - ob1@0.84.3: + ob1@0.84.4: dependencies: flow-enums-runtime: 0.0.6 @@ -11334,27 +11232,27 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.47.0 '@oxfmt/binding-win32-x64-msvc': 0.47.0 - oxlint@1.62.0: + oxlint@1.63.0: optionalDependencies: - '@oxlint/binding-android-arm-eabi': 1.62.0 - '@oxlint/binding-android-arm64': 1.62.0 - '@oxlint/binding-darwin-arm64': 1.62.0 - '@oxlint/binding-darwin-x64': 1.62.0 - '@oxlint/binding-freebsd-x64': 1.62.0 - '@oxlint/binding-linux-arm-gnueabihf': 1.62.0 - '@oxlint/binding-linux-arm-musleabihf': 1.62.0 - '@oxlint/binding-linux-arm64-gnu': 1.62.0 - '@oxlint/binding-linux-arm64-musl': 1.62.0 - '@oxlint/binding-linux-ppc64-gnu': 1.62.0 - '@oxlint/binding-linux-riscv64-gnu': 1.62.0 - '@oxlint/binding-linux-riscv64-musl': 1.62.0 - '@oxlint/binding-linux-s390x-gnu': 1.62.0 - '@oxlint/binding-linux-x64-gnu': 1.62.0 - '@oxlint/binding-linux-x64-musl': 1.62.0 - '@oxlint/binding-openharmony-arm64': 1.62.0 - '@oxlint/binding-win32-arm64-msvc': 1.62.0 - '@oxlint/binding-win32-ia32-msvc': 1.62.0 - '@oxlint/binding-win32-x64-msvc': 1.62.0 + '@oxlint/binding-android-arm-eabi': 1.63.0 + '@oxlint/binding-android-arm64': 1.63.0 + '@oxlint/binding-darwin-arm64': 1.63.0 + '@oxlint/binding-darwin-x64': 1.63.0 + '@oxlint/binding-freebsd-x64': 1.63.0 + '@oxlint/binding-linux-arm-gnueabihf': 1.63.0 + '@oxlint/binding-linux-arm-musleabihf': 1.63.0 + '@oxlint/binding-linux-arm64-gnu': 1.63.0 + '@oxlint/binding-linux-arm64-musl': 1.63.0 + '@oxlint/binding-linux-ppc64-gnu': 1.63.0 + '@oxlint/binding-linux-riscv64-gnu': 1.63.0 + '@oxlint/binding-linux-riscv64-musl': 1.63.0 + '@oxlint/binding-linux-s390x-gnu': 1.63.0 + '@oxlint/binding-linux-x64-gnu': 1.63.0 + '@oxlint/binding-linux-x64-musl': 1.63.0 + '@oxlint/binding-openharmony-arm64': 1.63.0 + '@oxlint/binding-win32-arm64-msvc': 1.63.0 + '@oxlint/binding-win32-ia32-msvc': 1.63.0 + '@oxlint/binding-win32-x64-msvc': 1.63.0 p-limit@2.3.0: dependencies: @@ -11405,7 +11303,7 @@ snapshots: path-scurry@2.0.2: dependencies: - lru-cache: 11.3.5 + lru-cache: 11.3.6 minipass: 7.1.3 picocolors@1.1.1: {} @@ -11436,7 +11334,7 @@ snapshots: postcss@8.4.49: dependencies: - nanoid: 3.3.11 + nanoid: 3.3.12 picocolors: 1.1.1 source-map-js: 1.2.1 @@ -11514,70 +11412,66 @@ snapshots: - bufferutil - utf-8-validate - react-dom@19.2.5(react@19.2.0): + react-dom@19.2.5(react@19.2.6): dependencies: - react: 19.2.0 + react: 19.2.6 scheduler: 0.27.0 react-fast-compare@3.2.2: {} - react-freeze@1.0.4(react@19.2.0): + react-freeze@1.0.4(react@19.2.6): dependencies: - react: 19.2.0 + react: 19.2.6 react-is@16.13.1: {} react-is@18.3.1: {} - react-is@19.2.5: {} + react-is@19.2.6: {} - react-native-gesture-handler@2.30.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + react-native-gesture-handler@2.31.2(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): 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.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - react-native-is-edge-to-edge@1.2.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + react-native-is-edge-to-edge@1.3.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - react-native-is-edge-to-edge@1.3.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + react-native-reanimated@4.3.0(react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + react-native-is-edge-to-edge: 1.3.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + react-native-worklets: 0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) + semver: 7.7.4 - react-native-reanimated@4.2.1(react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0))(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + react-native-safe-area-context@5.7.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - react-native-is-edge-to-edge: 1.2.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - react-native-worklets: 0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - semver: 7.7.3 + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - react-native-safe-area-context@5.6.2(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + react-native-screens@4.24.0(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - - react-native-screens@4.23.0(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): - dependencies: - react: 19.2.0 - react-freeze: 1.0.4(react@19.2.0) - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-freeze: 1.0.4(react@19.2.6) + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) warn-once: 0.1.1 - react-native-svg@15.15.4(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + react-native-svg@15.15.4(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: css-select: 5.2.2 css-tree: 1.1.3 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) warn-once: 0.1.1 - react-native-web@0.21.2(react-dom@19.2.5(react@19.2.0))(react@19.2.0): + react-native-web@0.21.2(react-dom@19.2.5(react@19.2.6))(react@19.2.6): dependencies: '@babel/runtime': 7.29.2 '@react-native/normalize-colors': 0.74.89 @@ -11586,48 +11480,49 @@ snapshots: memoize-one: 6.0.0 nullthrows: 1.1.1 postcss-value-parser: 4.2.0 - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) styleq: 0.1.3 transitivePeerDependencies: - encoding - react-native-webview@13.16.1(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + react-native-webview@13.16.1(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: escape-string-regexp: 4.0.0 invariant: 2.2.4 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) - react-native-worklets@0.7.4(@babel/core@7.29.0)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0): + react-native-worklets@0.8.3(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6): dependencies: '@babel/core': 7.29.0 '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-classes': 7.28.4(@babel/core@7.29.0) - '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.29.0) - '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) - '@babel/preset-typescript': 7.27.1(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@react-native/metro-config': 0.85.2(@babel/core@7.29.0) convert-source-map: 2.0.0 - react: 19.2.0 - react-native: 0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0) - semver: 7.7.3 + react: 19.2.6 + react-native: 0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6) + semver: 7.7.4 transitivePeerDependencies: - supports-color - react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0): + react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6): dependencies: '@jest/create-cache-key-function': 29.7.0 - '@react-native/assets-registry': 0.83.6 - '@react-native/codegen': 0.83.6(@babel/core@7.29.0) - '@react-native/community-cli-plugin': 0.83.6(@react-native/metro-config@0.85.2(@babel/core@7.29.0)) - '@react-native/gradle-plugin': 0.83.6 - '@react-native/js-polyfills': 0.83.6 - '@react-native/normalize-colors': 0.83.6 - '@react-native/virtualized-lists': 0.83.6(@types/react@19.2.14)(react-native@0.83.6(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) + '@react-native/assets-registry': 0.83.9 + '@react-native/codegen': 0.83.9(@babel/core@7.29.0) + '@react-native/community-cli-plugin': 0.83.9(@react-native/metro-config@0.85.2(@babel/core@7.29.0)) + '@react-native/gradle-plugin': 0.83.9 + '@react-native/js-polyfills': 0.83.9 + '@react-native/normalize-colors': 0.83.9 + '@react-native/virtualized-lists': 0.83.9(@types/react@19.2.14)(react-native@0.83.9(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.6))(react@19.2.6) abort-controller: 3.0.0 anser: 1.4.10 ansi-regex: 5.0.1 @@ -11641,12 +11536,12 @@ snapshots: invariant: 2.2.4 jest-environment-node: 29.7.0 memoize-one: 5.2.1 - metro-runtime: 0.83.6 - metro-source-map: 0.83.6 + metro-runtime: 0.83.7 + metro-source-map: 0.83.7 nullthrows: 1.1.1 pretty-format: 29.7.0 promise: 8.3.0 - react: 19.2.0 + react: 19.2.6 react-devtools-core: 6.1.5 react-refresh: 0.14.2 regenerator-runtime: 0.13.11 @@ -11666,87 +11561,42 @@ snapshots: - supports-color - utf-8-validate - react-native@0.85.2(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0): - dependencies: - '@react-native/assets-registry': 0.85.2 - '@react-native/codegen': 0.85.2(@babel/core@7.29.0) - '@react-native/community-cli-plugin': 0.85.2(@react-native/metro-config@0.85.2(@babel/core@7.29.0)) - '@react-native/gradle-plugin': 0.85.2 - '@react-native/js-polyfills': 0.85.2 - '@react-native/normalize-colors': 0.85.2 - '@react-native/virtualized-lists': 0.85.2(@types/react@19.2.14)(react-native@0.85.2(@babel/core@7.29.0)(@react-native/metro-config@0.85.2(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.0))(react@19.2.0) - abort-controller: 3.0.0 - anser: 1.4.10 - ansi-regex: 5.0.1 - babel-plugin-syntax-hermes-parser: 0.33.3 - base64-js: 1.5.1 - commander: 12.1.0 - flow-enums-runtime: 0.0.6 - hermes-compiler: 250829098.0.10 - invariant: 2.2.4 - memoize-one: 5.2.1 - metro-runtime: 0.84.3 - metro-source-map: 0.84.3 - nullthrows: 1.1.1 - pretty-format: 29.7.0 - promise: 8.3.0 - react: 19.2.0 - react-devtools-core: 6.1.5 - react-refresh: 0.14.2 - regenerator-runtime: 0.13.11 - scheduler: 0.27.0 - semver: 7.7.4 - stacktrace-parser: 0.1.11 - tinyglobby: 0.2.16 - whatwg-fetch: 3.6.20 - ws: 7.5.10 - yargs: 17.7.2 - optionalDependencies: - '@types/react': 19.2.14 - 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.14)(react@19.2.0): + react-remove-scroll-bar@2.3.8(@types/react@19.2.14)(react@19.2.6): dependencies: - react: 19.2.0 - react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.6) tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.14 - react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.0): + react-remove-scroll@2.7.2(@types/react@19.2.14)(react@19.2.6): dependencies: - react: 19.2.0 - react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.0) - react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.0) + react: 19.2.6 + react-remove-scroll-bar: 2.3.8(@types/react@19.2.14)(react@19.2.6) + react-style-singleton: 2.2.3(@types/react@19.2.14)(react@19.2.6) tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.0) - use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.0) + use-callback-ref: 1.3.3(@types/react@19.2.14)(react@19.2.6) + use-sidecar: 1.1.3(@types/react@19.2.14)(react@19.2.6) optionalDependencies: '@types/react': 19.2.14 - react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.0): + react-style-singleton@2.2.3(@types/react@19.2.14)(react@19.2.6): dependencies: get-nonce: 1.0.1 - react: 19.2.0 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.14 - react-test-renderer@19.2.0(react@19.2.0): + react-test-renderer@19.2.0(react@19.2.6): dependencies: - react: 19.2.0 - react-is: 19.2.5 + react: 19.2.6 + react-is: 19.2.6 scheduler: 0.27.0 - react@19.2.0: {} + react@19.2.6: {} readdirp@3.6.0: dependencies: @@ -11824,14 +11674,14 @@ snapshots: resolve@1.22.12: dependencies: es-errors: 1.3.0 - is-core-module: 2.16.1 + is-core-module: 2.16.2 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 resolve@2.0.0-next.6: dependencies: es-errors: 1.3.0 - is-core-module: 2.16.1 + is-core-module: 2.16.2 node-exports-info: 1.6.0 object-keys: 1.1.1 path-parse: 1.0.7 @@ -11883,8 +11733,6 @@ snapshots: semver@7.6.3: {} - semver@7.7.3: {} - semver@7.7.4: {} send@0.19.2: @@ -12367,28 +12215,28 @@ snapshots: querystringify: 2.2.0 requires-port: 1.0.0 - use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.0): + use-callback-ref@1.3.3(@types/react@19.2.14)(react@19.2.6): dependencies: - react: 19.2.0 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.14 - use-latest-callback@0.2.6(react@19.2.0): + use-latest-callback@0.2.6(react@19.2.6): dependencies: - react: 19.2.0 + react: 19.2.6 - use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.0): + use-sidecar@1.1.3(@types/react@19.2.14)(react@19.2.6): dependencies: detect-node-es: 1.1.0 - react: 19.2.0 + react: 19.2.6 tslib: 2.8.1 optionalDependencies: '@types/react': 19.2.14 - use-sync-external-store@1.6.0(react@19.2.0): + use-sync-external-store@1.6.0(react@19.2.6): dependencies: - react: 19.2.0 + react: 19.2.6 utils-merge@1.0.1: {} @@ -12404,11 +12252,11 @@ snapshots: vary@1.1.2: {} - vaul@1.1.2(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0): + vaul@1.1.2(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6): dependencies: - '@radix-ui/react-dialog': 1.1.15(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.0))(react@19.2.0) - react: 19.2.0 - react-dom: 19.2.5(react@19.2.0) + '@radix-ui/react-dialog': 1.1.15(@types/react@19.2.14)(react-dom@19.2.5(react@19.2.6))(react@19.2.6) + react: 19.2.6 + react-dom: 19.2.5(react@19.2.6) transitivePeerDependencies: - '@types/react' - '@types/react-dom' @@ -12539,7 +12387,7 @@ snapshots: yallist@3.1.1: {} - yaml@2.8.3: {} + yaml@2.8.4: {} yargs-parser@21.1.1: {} @@ -12557,15 +12405,15 @@ snapshots: zod@3.25.76: {} - zod@4.3.6: {} + zod@4.4.3: {} - zustand@5.0.12(@types/react@19.2.14)(react@19.2.0)(use-sync-external-store@1.6.0(react@19.2.0)): + zustand@5.0.13(@types/react@19.2.14)(react@19.2.6)(use-sync-external-store@1.6.0(react@19.2.6)): optionalDependencies: '@types/react': 19.2.14 - react: 19.2.0 - use-sync-external-store: 1.6.0(react@19.2.0) + react: 19.2.6 + use-sync-external-store: 1.6.0(react@19.2.6) - zxing-wasm@3.0.2(@types/emscripten@1.41.5): + zxing-wasm@3.0.3(@types/emscripten@1.41.5): dependencies: '@types/emscripten': 1.41.5 type-fest: 5.6.0 diff --git a/mobile/src/cache/home-snapshot-cache.ts b/mobile/src/cache/home-snapshot-cache.ts new file mode 100644 index 00000000000..57256be8ea0 --- /dev/null +++ b/mobile/src/cache/home-snapshot-cache.ts @@ -0,0 +1,66 @@ +// Why: persist the data needed to render the home page so cold-start / +// resume-from-background paints instantly with the last known good +// values, then updates in place when fresh RPC data arrives. Without +// this, Resume and Account-usage cards flash empty for ~1s while the +// WebSocket reconnects and the first responses come back. +import AsyncStorage from '@react-native-async-storage/async-storage' +import type { AccountsSnapshot } from '../components/AccountUsage' + +const STORAGE_KEY = 'orca:home-snapshot:v1' + +type WorktreeSummary = { + worktreeId: string + repo: string + branch: string + displayName: string + liveTerminalCount: number + status?: 'working' | 'active' | 'permission' | 'done' | 'inactive' +} + +type HostWorktreeInfo = { + hostId: string + totalWorktrees: number + activeCount: number + lastActiveWorktree: WorktreeSummary | null +} + +export type HomeSnapshot = { + worktreeInfo: Record + accountsByHost: Record + savedAt: number +} + +let memoryCache: HomeSnapshot | null = null +let writeTimer: ReturnType | null = null + +export async function loadHomeSnapshot(): Promise { + if (memoryCache) return memoryCache + try { + const raw = await AsyncStorage.getItem(STORAGE_KEY) + if (!raw) return null + const parsed = JSON.parse(raw) as HomeSnapshot + if ( + typeof parsed !== 'object' || + parsed === null || + typeof parsed.worktreeInfo !== 'object' || + typeof parsed.accountsByHost !== 'object' + ) { + return null + } + memoryCache = parsed + return parsed + } catch { + return null + } +} + +// Why: throttle writes so a flurry of streamed account-snapshot updates +// (one per provider fetch finishing) doesn't hammer AsyncStorage. +export function saveHomeSnapshot(snapshot: HomeSnapshot): void { + memoryCache = snapshot + if (writeTimer) clearTimeout(writeTimer) + writeTimer = setTimeout(() => { + writeTimer = null + void AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(snapshot)).catch(() => {}) + }, 250) +} diff --git a/mobile/src/components/AccountUsage.tsx b/mobile/src/components/AccountUsage.tsx new file mode 100644 index 00000000000..0ff706638b9 --- /dev/null +++ b/mobile/src/components/AccountUsage.tsx @@ -0,0 +1,155 @@ +import { View, Text, StyleSheet, ActivityIndicator } from 'react-native' +import { colors, spacing, typography } from '../theme/mobile-theme' + +// Why: keep these shapes in lockstep with src/shared/types.ts and +// src/shared/rate-limit-types.ts. We don't import from desktop here because +// the mobile bundle must not pull in Electron-coupled type files. +export type RateLimitWindow = { + usedPercent: number + windowMinutes: number + resetsAt: number | null + resetDescription: string | null +} + +export type ProviderRateLimits = { + provider: 'claude' | 'codex' | 'gemini' | 'opencode-go' + session: RateLimitWindow | null + weekly: RateLimitWindow | null + monthly?: RateLimitWindow | null + updatedAt: number + error: string | null + status: 'idle' | 'fetching' | 'ok' | 'error' | 'unavailable' +} + +export type InactiveAccountUsage = { + accountId: string + claude: ProviderRateLimits | null + updatedAt: number + isFetching: boolean +} + +export type ClaudeAccountSummary = { + id: string + email: string + organizationName?: string | null +} + +export type CodexAccountSummary = { + id: string + email: string + workspaceLabel?: string | null +} + +export type AccountsSnapshot = { + claude: { accounts: ClaudeAccountSummary[]; activeAccountId: string | null } + codex: { accounts: CodexAccountSummary[]; activeAccountId: string | null } + rateLimits: { + claude: ProviderRateLimits | null + codex: ProviderRateLimits | null + inactiveClaudeAccounts: InactiveAccountUsage[] + inactiveCodexAccounts: InactiveAccountUsage[] + } +} + +export type ProviderKey = 'claude' | 'codex' + +export function getActiveProviderRateLimits( + snapshot: AccountsSnapshot, + provider: ProviderKey +): ProviderRateLimits | null { + return provider === 'claude' ? snapshot.rateLimits.claude : snapshot.rateLimits.codex +} + +export function getInactiveProviderUsage( + snapshot: AccountsSnapshot, + provider: ProviderKey, + accountId: string +): InactiveAccountUsage | null { + const list = + provider === 'claude' + ? snapshot.rateLimits.inactiveClaudeAccounts + : snapshot.rateLimits.inactiveCodexAccounts + return list.find((u) => u.accountId === accountId) ?? null +} + +// Why: matches desktop StatusBar convention — bars show percent remaining +// (so a fresh account renders full, a depleted one renders empty), not +// percent used. Color thresholds invert accordingly. +export function UsageBar({ + label, + usedPercent, + unavailable, + loading +}: { + label: string + usedPercent: number | null + unavailable: boolean + loading?: boolean +}) { + const remaining = usedPercent == null ? null : Math.max(0, Math.min(100, 100 - usedPercent)) + const barColor = + remaining == null + ? colors.textMuted + : remaining <= 10 + ? colors.statusRed + : remaining <= 30 + ? colors.statusAmber + : colors.statusGreen + return ( + + {label} + + + + {loading ? ( + + ) : ( + + {unavailable || remaining == null ? '—' : `${Math.round(remaining)}%`} + + )} + + ) +} + +const styles = StyleSheet.create({ + usageBar: { + flexDirection: 'row', + alignItems: 'center', + gap: spacing.xs, + flex: 1 + }, + usageLabel: { + fontSize: typography.metaSize, + color: colors.textMuted, + width: 22 + }, + usageTrack: { + flex: 1, + height: 6, + borderRadius: 3, + backgroundColor: colors.bgRaised, + overflow: 'hidden' + }, + usageFill: { + height: '100%', + borderRadius: 3 + }, + usageValue: { + fontSize: typography.metaSize, + color: colors.textSecondary, + width: 36, + textAlign: 'right' + }, + usageSpinner: { + width: 36 + } +}) diff --git a/mobile/src/components/AgentIcons.tsx b/mobile/src/components/AgentIcons.tsx new file mode 100644 index 00000000000..714fe76e0f5 --- /dev/null +++ b/mobile/src/components/AgentIcons.tsx @@ -0,0 +1,30 @@ +import Svg, { Path } from 'react-native-svg' +import { colors } from '../theme/mobile-theme' + +// Why: SVG paths sourced from the desktop codebase +// (src/renderer/src/components/status-bar/icons.tsx) so mobile and desktop +// stay visually identical for Claude/Codex branding. + +export function ClaudeIcon({ size = 16 }: { size?: number }) { + return ( + + + + ) +} + +export function OpenAIIcon({ size = 16, color }: { size?: number; color?: string }) { + return ( + + + + ) +} diff --git a/mobile/src/components/AgentSpinner.tsx b/mobile/src/components/AgentSpinner.tsx index 734cb8bbc6d..673bbff049e 100644 --- a/mobile/src/components/AgentSpinner.tsx +++ b/mobile/src/components/AgentSpinner.tsx @@ -1,15 +1,20 @@ import { useEffect, useRef } from 'react' import { Animated, Easing, StyleSheet, View } from 'react-native' -import { colors } from '../theme/mobile-theme' type WorktreeStatus = 'working' | 'active' | 'permission' | 'done' | 'inactive' +// Why: colors and sizing are 1:1 with the desktop StatusIndicator +// (src/renderer/src/components/sidebar/StatusIndicator.tsx) so the mobile +// worktree list reads identically to the sidebar — same yellow spinner for +// 'working', same emerald dot for 'active'/'done', same neutral-500 @ 40% +// for 'inactive', same red for 'permission'. Diverging palettes here lose +// the design intent ('moving' vs 'alive' vs 'completed') the desktop encodes. const STATUS_COLORS: Record = { - working: colors.statusGreen, - active: colors.statusGreen, - permission: colors.statusRed, - done: '#38bdf8', - inactive: '#666666' + working: '#eab308', + active: '#10b981', + done: '#10b981', + permission: '#ef4444', + inactive: 'rgba(115,115,115,0.4)' } export function AgentSpinner({ status }: { status: WorktreeStatus }) { @@ -39,23 +44,39 @@ export function AgentSpinner({ status }: { status: WorktreeStatus }) { outputRange: ['0deg', '360deg'] }) return ( - + + + ) } - return + return ( + + + + ) } const styles = StyleSheet.create({ + // Why: 12x12 wrapper centered around an 8x8 inner glyph mirrors the + // desktop's `inline-flex h-3 w-3 ... items-center justify-center` shell + // around `size-2` indicator — keeps row height/baseline alignment stable + // across status transitions. + wrapper: { + width: 12, + height: 12, + alignItems: 'center', + justifyContent: 'center' + }, dot: { width: 8, height: 8, borderRadius: 4 }, spinner: { - width: 10, - height: 10, - borderRadius: 5, + width: 8, + height: 8, + borderRadius: 4, borderWidth: 2, borderTopColor: 'transparent' } diff --git a/mobile/src/components/ConnectionLog.tsx b/mobile/src/components/ConnectionLog.tsx new file mode 100644 index 00000000000..3722ba0db25 --- /dev/null +++ b/mobile/src/components/ConnectionLog.tsx @@ -0,0 +1,141 @@ +import { useEffect, useRef } from 'react' +import { ScrollView, StyleSheet, Text, View } from 'react-native' +import type { ConnectionLogEntry } from '../transport/types' +import { colors, radii, spacing, typography } from '../theme/mobile-theme' + +type Props = { + entries: ConnectionLogEntry[] + // Tag printed before the first entry so it's clear what's being logged + // (e.g. 'Pairing' vs 'Reconnect'). + title?: string +} + +const LEVEL_COLOR: Record = { + info: colors.textSecondary, + success: colors.statusGreen, + warn: colors.statusAmber, + error: colors.statusRed +} + +const LEVEL_GLYPH: Record = { + info: '•', + success: '✓', + warn: '!', + error: '✕' +} + +function formatTime(ts: number, baseTs: number): string { + // Why: show elapsed seconds since the first entry — absolute wall-clock + // time isn't actionable when debugging "why is connecting stuck". + const elapsed = Math.max(0, ts - baseTs) / 1000 + if (elapsed < 10) return `+${elapsed.toFixed(2)}s` + if (elapsed < 100) return `+${elapsed.toFixed(1)}s` + return `+${Math.round(elapsed)}s` +} + +export function ConnectionLog({ entries, title }: Props) { + const scrollRef = useRef(null) + + // Why: keep the latest entry visible while logs grow during a slow + // connect. Skip on empty so the title row doesn't jump. + useEffect(() => { + if (entries.length === 0) return + const id = setTimeout(() => scrollRef.current?.scrollToEnd({ animated: true }), 16) + return () => clearTimeout(id) + }, [entries.length]) + + if (entries.length === 0) return null + const baseTs = entries[0]!.ts + + return ( + + {title && {title}} + + {entries.map((entry) => ( + + {formatTime(entry.ts, baseTs)} + + {LEVEL_GLYPH[entry.level]} + + + + {entry.message} + + {entry.detail && ( + + {entry.detail} + + )} + + + ))} + + + ) +} + +const styles = StyleSheet.create({ + container: { + width: '100%', + maxHeight: 240, + backgroundColor: colors.bgPanel, + borderRadius: radii.card, + borderWidth: StyleSheet.hairlineWidth, + borderColor: colors.borderSubtle, + paddingVertical: spacing.sm, + paddingHorizontal: spacing.md + }, + title: { + fontSize: typography.metaSize, + fontFamily: typography.monoFamily, + color: colors.textMuted, + textTransform: 'uppercase', + letterSpacing: 1, + marginBottom: spacing.xs + }, + scroll: { + maxHeight: 200 + }, + scrollContent: { + gap: 6 + }, + row: { + flexDirection: 'row', + alignItems: 'flex-start', + gap: spacing.sm + }, + timestamp: { + fontFamily: typography.monoFamily, + fontSize: typography.metaSize, + color: colors.textMuted, + width: 52, + paddingTop: 1 + }, + glyph: { + fontFamily: typography.monoFamily, + fontSize: typography.metaSize, + width: 12, + textAlign: 'center', + paddingTop: 1 + }, + rowText: { + flex: 1 + }, + message: { + fontFamily: typography.monoFamily, + fontSize: typography.metaSize, + lineHeight: 16 + }, + detail: { + fontFamily: typography.monoFamily, + fontSize: 11, + color: colors.textMuted, + lineHeight: 14, + marginTop: 1 + } +}) diff --git a/mobile/src/components/NewWorktreeModal.tsx b/mobile/src/components/NewWorktreeModal.tsx index f0841054739..a7e7d2579b3 100644 --- a/mobile/src/components/NewWorktreeModal.tsx +++ b/mobile/src/components/NewWorktreeModal.tsx @@ -16,6 +16,7 @@ import type { RpcClient } from '../transport/rpc-client' import type { RpcSuccess } from '../transport/types' import { colors, spacing, radii, typography } from '../theme/mobile-theme' import { BottomDrawer } from './BottomDrawer' +import { ClaudeIcon, OpenAIIcon } from './AgentIcons' import { getSuggestedCreatureName } from './worktree-name-suggestion' type Repo = { @@ -91,34 +92,10 @@ const AGENT_COMMANDS: Record = { // ── Agent icons ───────────────────────────────────────────────────── // SVG paths sourced from the desktop codebase: -// Claude & OpenAI: src/renderer/src/components/status-bar/icons.tsx +// Claude & OpenAI: shared in ./AgentIcons.tsx // Pi & Aider: src/renderer/src/lib/agent-catalog.tsx // Agents with a faviconDomain use Google's favicon service (same as desktop). -function ClaudeIcon({ size = 16 }: { size?: number }) { - return ( - - - - ) -} - -function OpenAIIcon({ size = 16 }: { size?: number }) { - return ( - - - - ) -} - function PiIcon({ size = 16 }: { size?: number }) { return ( diff --git a/mobile/src/components/ProtocolBlockScreen.tsx b/mobile/src/components/ProtocolBlockScreen.tsx new file mode 100644 index 00000000000..368976676f1 --- /dev/null +++ b/mobile/src/components/ProtocolBlockScreen.tsx @@ -0,0 +1,105 @@ +import { Linking, Pressable, StyleSheet, Text, View } from 'react-native' +import { router } from 'expo-router' +import { colors, radii, spacing, typography } from '../theme/mobile-theme' +import type { CompatVerdict } from '../transport/protocol-compat' +import { MOBILE_PROTOCOL_VERSION } from '../transport/protocol-version' + +const RELEASES_URL = 'https://github.com/stablyai/orca/releases' + +type Props = { + verdict: Extract +} + +export function ProtocolBlockScreen({ verdict }: Props) { + const isMobileTooOld = verdict.reason === 'mobile-too-old' + + const title = isMobileTooOld ? 'Update Orca Mobile' : 'Update Orca desktop' + const body = isMobileTooOld + ? `The Orca desktop on this host requires Orca Mobile v${verdict.requiredMobileVersion ?? '?'}+. You have v${MOBILE_PROTOCOL_VERSION}.\n\nUpdate Orca Mobile from the App Store to continue.` + : `Orca Mobile requires Orca desktop v${verdict.requiredDesktopVersion ?? '?'}+ to use this host. The desktop is reporting v${verdict.desktopVersion}.` + + return ( + + + {title} + {body} + {/* Why: only desktop side has a deep-link target today — + App Store ID isn't published yet, so mobile-too-old is text-only. */} + {!isMobileTooOld ? ( + [styles.primaryButton, pressed && styles.pressed]} + onPress={() => { + void Linking.openURL(RELEASES_URL) + }} + > + Open GitHub Releases + + ) : null} + [styles.secondaryButton, pressed && styles.pressed]} + onPress={() => { + // Why: route back to the host list so the user can pair a + // different host instead of getting trapped on this screen. + router.replace('/') + }} + > + Pair a different host + + + + ) +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.bgBase, + justifyContent: 'center', + paddingHorizontal: spacing.lg + }, + card: { + backgroundColor: colors.bgPanel, + borderRadius: radii.card, + padding: spacing.lg, + borderWidth: 1, + borderColor: colors.borderSubtle + }, + title: { + fontSize: typography.titleSize, + fontWeight: '700', + color: colors.textPrimary, + marginBottom: spacing.sm + }, + body: { + fontSize: typography.bodySize, + color: colors.textSecondary, + lineHeight: 20, + marginBottom: spacing.lg + }, + primaryButton: { + backgroundColor: colors.textPrimary, + paddingVertical: spacing.sm + 2, + borderRadius: radii.button, + alignItems: 'center', + marginBottom: spacing.sm + }, + primaryButtonText: { + fontSize: typography.bodySize, + fontWeight: '600', + color: colors.bgBase + }, + secondaryButton: { + backgroundColor: colors.bgRaised, + paddingVertical: spacing.sm + 2, + borderRadius: radii.button, + alignItems: 'center' + }, + secondaryButtonText: { + fontSize: typography.bodySize, + fontWeight: '600', + color: colors.textPrimary + }, + pressed: { + opacity: 0.7 + } +}) diff --git a/mobile/src/components/StatusDot.tsx b/mobile/src/components/StatusDot.tsx index 63777890d70..a29b5e95eba 100644 --- a/mobile/src/components/StatusDot.tsx +++ b/mobile/src/components/StatusDot.tsx @@ -1,18 +1,36 @@ import { View, StyleSheet } from 'react-native' import { colors } from '../theme/mobile-theme' import type { ConnectionState } from '../transport/types' +import type { ConnectionVerdict } from '../transport/connection-health' const stateColors: Record = { connected: colors.statusGreen, connecting: colors.statusAmber, handshaking: colors.statusAmber, reconnecting: colors.statusAmber, - disconnected: colors.statusRed, + disconnected: colors.textMuted, 'auth-failed': colors.statusRed } -export function StatusDot({ state }: { state: ConnectionState }) { - return +// Why: when caller passes a verdict, the dot color reflects the verdict's +// severity instead of the raw transport state. This avoids the "amber dot +// next to red 'Can't reach desktop' label" mismatch — the underlying +// transport is still 'reconnecting' (amber) but the user-visible meaning +// has escalated to error (red). +export function StatusDot({ + state, + verdict +}: { + state: ConnectionState + verdict?: ConnectionVerdict +}) { + const color = + verdict?.kind === 'unreachable' || verdict?.kind === 'auth-failed' + ? colors.statusRed + : verdict?.kind === 'warning' + ? colors.statusAmber + : (stateColors[state] ?? colors.textMuted) + return } const styles = StyleSheet.create({ diff --git a/mobile/src/components/TextInputModal.tsx b/mobile/src/components/TextInputModal.tsx index 70a9f323f56..ae6f3ee3e7f 100644 --- a/mobile/src/components/TextInputModal.tsx +++ b/mobile/src/components/TextInputModal.tsx @@ -41,23 +41,19 @@ export function TextInputModal({ {message ? {message} : null} - - - - - + { + const { status, canAskAgain } = await Notifications.getPermissionsAsync() + return { + granted: status === 'granted', + status, + canAskAgain + } +} // Why: permissions must be requested before scheduling any local notification. -// Cache the result so we only prompt once per app session. +// Read the OS state every time because users can change it in Settings while +// Orca remains alive in the background. export async function ensureNotificationPermissions(): Promise { - if (permissionGranted !== null) return permissionGranted - - const { status: existingStatus } = await Notifications.getPermissionsAsync() - if (existingStatus === 'granted') { - permissionGranted = true + const existing = await getNotificationPermissionState() + if (existing.granted) { return true } const { status } = await Notifications.requestPermissionsAsync() - permissionGranted = status === 'granted' - return permissionGranted + return status === 'granted' } function configureNotificationChannel(): void { diff --git a/mobile/src/platform/haptics.ts b/mobile/src/platform/haptics.ts index 72bf2443a82..265283898d9 100644 --- a/mobile/src/platform/haptics.ts +++ b/mobile/src/platform/haptics.ts @@ -11,3 +11,35 @@ export function triggerMediumImpact(): void { void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium).catch(() => {}) } } + +export function triggerSelection(): void { + if (Platform.OS === 'android') { + void Haptics.performAndroidHapticsAsync(Haptics.AndroidHaptics.Gesture_Start).catch(() => {}) + } else { + void Haptics.selectionAsync().catch(() => {}) + } +} + +export function triggerSuccess(): void { + if (Platform.OS === 'android') { + void Haptics.performAndroidHapticsAsync(Haptics.AndroidHaptics.Confirm).catch(() => {}) + } else { + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success).catch(() => {}) + } +} + +export function triggerError(): void { + if (Platform.OS === 'android') { + void Haptics.performAndroidHapticsAsync(Haptics.AndroidHaptics.Reject).catch(() => {}) + } else { + void Haptics.notificationAsync(Haptics.NotificationFeedbackType.Error).catch(() => {}) + } +} + +export function triggerEdgeBump(): void { + if (Platform.OS === 'android') { + void Haptics.performAndroidHapticsAsync(Haptics.AndroidHaptics.Clock_Tick).catch(() => {}) + } else { + void Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light).catch(() => {}) + } +} diff --git a/mobile/src/storage/preferences.ts b/mobile/src/storage/preferences.ts index efd4dc75c9b..7a588902d86 100644 --- a/mobile/src/storage/preferences.ts +++ b/mobile/src/storage/preferences.ts @@ -4,13 +4,18 @@ const PINS_PREFIX = 'orca:pins:' const PREFS_PREFIX = 'orca:prefs:' const NOTIF_KEY = 'orca:pushNotificationsEnabled' +// Why: default-off so the iOS notification permission prompt never +// fires until the user explicitly opts in via Settings → Notifications. +// Apple's review guideline 4.5.4 and HIG both prefer user-initiated +// permission prompts; default-on would fire the prompt the moment the +// desktop sent its first notification, which can read as unsolicited. export async function loadPushNotificationsEnabled(): Promise { try { const raw = await AsyncStorage.getItem(NOTIF_KEY) - if (raw === null) return true + if (raw === null) return false return raw === 'true' } catch { - return true + return false } } diff --git a/mobile/src/terminal/TerminalWebView.tsx b/mobile/src/terminal/TerminalWebView.tsx index da19da50c85..52e199d0e8d 100644 --- a/mobile/src/terminal/TerminalWebView.tsx +++ b/mobile/src/terminal/TerminalWebView.tsx @@ -4,18 +4,38 @@ import { WebView } from 'react-native-webview' import type { WebViewMessageEvent } from 'react-native-webview' import { colors } from '../theme/mobile-theme' +export type TerminalModes = { + bracketedPasteMode: boolean + altScreen: boolean +} + +export type TerminalSelectionEvents = { + onSelectionMode?: (active: boolean) => void + onSelectionCopy?: (text: string) => void + onSelectionEvicted?: () => void + onModesChanged?: (modes: TerminalModes) => void + onHaptic?: (kind: 'selection' | 'success' | 'error' | 'edge-bump') => void +} + export type TerminalWebViewHandle = { write: (data: string) => void init: (cols: number, rows: number, initialData?: string) => void clear: () => void measureFitDimensions: (containerHeight?: number) => Promise<{ cols: number; rows: number } | null> resetZoom: () => void + cancelSelect: () => void + doSelectAll: () => void + // Why: lets callers await the WebView-side `init` rAF chain (term.open + // → renderService population → first paint) so a follow-up measure + // doesn't race ahead and find term=null or cellWidth=0. Resolves on + // the next 'ready' notify after the most recent init. + awaitReady: () => Promise } type Props = { style?: StyleProp onWebReady?: () => void -} +} & TerminalSelectionEvents type TerminalMessage = | { type: 'write'; id?: number; data: string } @@ -23,6 +43,8 @@ type TerminalMessage = | { type: 'clear'; id?: number } | { type: 'measure'; id?: number; containerHeight?: number } | { type: 'reset-zoom'; id?: number } + | { type: 'cancel-select'; id?: number } + | { type: 'do-select-all'; id?: number } // Why: TUI apps (Claude Code / Ink) emit escape codes with absolute cursor // positioning designed for the desktop's terminal dimensions (~150+ cols). @@ -58,12 +80,90 @@ const XTERM_HTML = ` display: inline-block; } .xterm { -webkit-user-select: none; user-select: none; } + /* Why: selection overlay sits in unscaled viewport coords, above the + transformed surface, so handle hit areas and Copy menu positions + don't depend on getTotalScale() for their on-screen size. */ + #selection-overlay { + position: fixed; + top: 0; left: 0; right: 0; bottom: 0; + pointer-events: none; + z-index: 10; + display: none; + } + #selection-overlay.active { display: block; } + .sel-handle { + position: absolute; + width: 44px; height: 44px; + margin-left: -22px; margin-top: -22px; + pointer-events: auto; + background: transparent; + } + .sel-handle::before { + content: ''; + position: absolute; + left: 50%; top: 22px; + transform: translateX(-50%); + width: 14px; height: 14px; + background: #7aa2f7; + border-radius: 50%; + border: 2px solid #c0caf5; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.5); + } + .sel-handle.start::before { top: 8px; } + .sel-handle.start::after { + content: ''; + position: absolute; + left: 50%; top: 22px; + transform: translateX(-50%); + width: 2px; height: 16px; + background: #7aa2f7; + } + .sel-handle.end::before { top: 22px; } + .sel-handle.end::after { + content: ''; + position: absolute; + left: 50%; top: 6px; + transform: translateX(-50%); + width: 2px; height: 16px; + background: #7aa2f7; + } + #sel-menu { + position: absolute; + pointer-events: auto; + background: #2a2f4a; + border-radius: 8px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5); + display: flex; + overflow: hidden; + transform: translateY(-100%); + margin-top: -12px; + user-select: none; + -webkit-user-select: none; + } + #sel-menu button { + background: transparent; + border: none; + color: #c0caf5; + font: 600 13px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + padding: 10px 16px; + cursor: pointer; + } + #sel-menu button:active { background: #414868; } + #sel-menu button + button { border-left: 1px solid #414868; }
+
+
+
+
+ + +
+
' }, + { line: 12, state: 'markup', pattern: '' }, + { line: 14, state: 'styleBody.css', pattern: '' } + ] + + checks.forEach((check) => { + const line = lines.at(check.line - 1) ?? '' + const stateRules = tokenizer[check.state] ?? tokenizer[check.state.split('.')[0]] + const matchedRule = stateRules.find((rule) => { + if (!isRuleEntry(rule)) { + return false + } + const [regexp] = rule + regexp.lastIndex = 0 + const match = regexp.exec(line) + return match !== null && match[0] === check.pattern + }) + if (!matchedRule || !isRuleEntry(matchedRule)) { + return + } + + const actionObject = getRuleAction(matchedRule) + + const nextState = actionObject?.next ? normalizeState(actionObject.next) : '-' + const nextEmbedded = actionObject?.nextEmbedded ?? '-' + const switchTo = actionObject?.switchTo ? normalizeState(actionObject.switchTo) : '-' + ruleActions.push( + `${check.line}:${check.state}:${check.pattern || ''} -> next=${nextState}, embedded=${nextEmbedded}, switch=${switchTo}` + ) + }) + + return ruleActions +} + +describe('registerAstroLanguage registration', () => { + it('registers the astro language, Monarch tokenizer, and configuration once', () => { + const languages: { id: string }[] = [{ id: 'typescript' }] + const register = vi.fn((entry: { id: string }) => { + languages.push({ id: entry.id }) + }) + const setMonarchTokensProvider = vi.fn() + const setLanguageConfiguration = vi.fn() + const getLanguages = vi.fn(() => languages) + const monacoMock = { + languages: { + register, + setMonarchTokensProvider, + setLanguageConfiguration, + getLanguages + } + } + + registerAstroLanguage(monacoMock as never) + registerAstroLanguage(monacoMock as never) + + expect(register).toHaveBeenCalledTimes(1) + expect(register).toHaveBeenCalledWith({ + id: 'astro', + extensions: ['.astro'], + aliases: ['Astro'] + }) + expect(setMonarchTokensProvider).toHaveBeenCalledTimes(1) + expect(setMonarchTokensProvider).toHaveBeenCalledWith('astro', astroMonarchLanguage) + expect(setLanguageConfiguration).toHaveBeenCalledTimes(1) + expect(setLanguageConfiguration).toHaveBeenCalledWith('astro', astroLanguageConfiguration) + }) +}) + +describe('astro tokenizer transitions', () => { + it('captures Astro tokenizer transitions for a representative component fixture', () => { + const fixture = `--- +import Layout from '../layouts/Layout.astro' +const title = 'Home' +--- + +

{title}

+ + + +` + + const ruleActions = collectFixtureRuleActions(fixture) + + expect(ruleActions).toMatchInlineSnapshot(` + [ + "1:root:--- -> next=-, embedded=typescript, switch=frontmatter", + "4:frontmatter:--- -> next=-, embedded=@pop, switch=markupReenter", + "6:markupReenter: -> next=-, embedded=html, switch=markup", + "6:markup:{ -> next=-, embedded=@pop, switch=astroExpressionEnter", + "6:astroExpression:} -> next=-, embedded=@pop, switch=markupReenter", + "8:markup: -> next=-, embedded=@pop, switch=markupReenter", + "12:markup: -> next=-, embedded=@pop, switch=markupReenter", + ] + `) + }) +}) + +describe('astro tokenizer regressions', () => { + // Regression: a file that opens with a markup expression like `{title}` has + // no html embed active yet. If `root` itself ever emitted `nextEmbedded: + // '@pop'` Monaco would throw "cannot pop embedded language if not inside + // one" before any push had occurred. Enforce the invariant directly. + it('never pops an embedded language from the root state', () => { + const tokenizer = astroMonarchLanguage.tokenizer as Record + const popRules = tokenizer.root.filter((rule) => { + if (!isRuleEntry(rule)) { + return false + } + return getRuleAction(rule)?.nextEmbedded === '@pop' + }) + expect(popRules).toHaveLength(0) + }) + + // Regression (verified live in the Electron app): when entry from root went + // straight to `@markup` with `nextEmbedded: 'html'`, while the embed-pop + // path also went via `@markupReenter`, Monarch's nested tokenizer reported + // "cannot pop embedded language if not inside one" on `{expr}` in markup. + // Routing every push of the html embed through `markupReenter` keeps the + // embed-stack invariant identical for every entry into `markup`. + it('routes all entries into markup through markupReenter', () => { + expect(findRuleAction('root', '

Hello

')).toMatchObject({ + switchTo: '@markupReenter' + }) + expect(findRuleAction('root', '{title}')).toMatchObject({ + switchTo: '@markupReenter' + }) + expect(findRuleAction('markupReenter', '

Hello

')).toMatchObject({ + switchTo: '@markup', + nextEmbedded: 'html' + }) + }) + + // Regression: while the html embed is active, only parent rules whose action + // pops the embed are consulted before delegating to html. The `markup` + // state must wire `nextEmbedded: '@pop'` on the structural rules so a + // trailing `